feat(linkedin-studio): SB-S3c — cross-silo id-threading + post→analytics assembler [skip-docs]
Hub-side design: the published record now carries the specifics/trends ids it was built from (additive, omit-empty → byte-backward-compatible), and a new pure assembler (scripts/brain/src/assemble.ts + `brain assemble`) joins post↔analytics by normalized title-prefix + date with honest confidence tiers (high/low/none). Answers the arc's north-star query: which raw material actually performs? (specific → post → measured analytics). All four tributaries untouched (analytics READ-only via inlined raw-JSON, no package import); profile.md grammar untouched (the fact→post link stays OUT — C-1). The repeatable --specific/--trend ingest flags collect via a new collectRepeated helper, leaving parseFlags untouched. TDD: 19 new brain tests (ingest 4 + publish 3 + assemble 8 + cli 4), all SC1–SC12. brain 113/113, gate 95/0/0, BRAIN_TESTS_FLOOR 94→113, ASSERT_BASELINE_FLOOR unchanged at 80. Light-Voyage hardened (brief-review 5 FIX · plan-critic 1 BLOCK+4 MAJOR+4 MINOR · scope-guardian ALIGNED). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RigJBiRFNtFZKCz21qNbQ4
This commit is contained in:
parent
016d823f3b
commit
edd3e15ef7
12 changed files with 515 additions and 14 deletions
95
scripts/brain/tests/assemble.test.ts
Normal file
95
scripts/brain/tests/assemble.test.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import { describe, test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { mintContentId } from "../src/id.js";
|
||||
import type { PublishedRecord } from "../src/ingest.js";
|
||||
import { assemblePostGraph, type AnalyticsRowInput } from "../src/assemble.js";
|
||||
|
||||
const DATE = "2026-05-26";
|
||||
|
||||
function rec(body: string, over: Partial<PublishedRecord> = {}): PublishedRecord {
|
||||
return {
|
||||
id: mintContentId(body),
|
||||
provenance: "published",
|
||||
published_date: DATE,
|
||||
captured_at: "2026-06-23",
|
||||
source: "manual",
|
||||
specifics: [],
|
||||
trends: [],
|
||||
body,
|
||||
...over,
|
||||
};
|
||||
}
|
||||
|
||||
function row(title: string, over: Partial<AnalyticsRowInput> = {}): AnalyticsRowInput {
|
||||
return { title, publishedDate: DATE, metrics: { engagementRate: 4.2 }, ...over };
|
||||
}
|
||||
|
||||
// A body whose normalized opener is ≥ PREFIX_FLOOR (24) chars.
|
||||
const BODY = "Jeg lærte noe viktig om dømmekraft i dag.\n\nDel 1 av serien.";
|
||||
const FULL_TITLE = "Jeg lærte noe viktig om dømmekraft i dag.";
|
||||
|
||||
describe("SB-S3c assembler — assemblePostGraph (SC6/SC7/SC8)", () => {
|
||||
// SC6 — high confidence: body begins with the row title (≥floor) + same date.
|
||||
test("SC6: prefix-match + same date → high, whole row attached, specifics/trends surfaced", () => {
|
||||
const r = rec(BODY, { specifics: ["aaaaaaaaaaaa"], trends: ["bbbbbbbbbbbb"] });
|
||||
const graph = assemblePostGraph({ records: [r], analytics: [row(FULL_TITLE)] });
|
||||
assert.equal(graph.length, 1);
|
||||
assert.equal(graph[0].contentId, r.id);
|
||||
assert.equal(graph[0].match.confidence, "high");
|
||||
assert.deepEqual(graph[0].match.row, row(FULL_TITLE)); // the whole row reference (FIX 4)
|
||||
assert.deepEqual(graph[0].specifics, ["aaaaaaaaaaaa"]);
|
||||
assert.deepEqual(graph[0].trends, ["bbbbbbbbbbbb"]);
|
||||
});
|
||||
|
||||
// SC7 — low / none / below-floor / ellipsis near-miss.
|
||||
test("SC7a: no prefix match → none", () => {
|
||||
const graph = assemblePostGraph({
|
||||
records: [rec(BODY)],
|
||||
analytics: [row("Completely unrelated opening sentence here")],
|
||||
});
|
||||
assert.equal(graph[0].match.confidence, "none");
|
||||
assert.equal(graph[0].match.row, undefined);
|
||||
});
|
||||
|
||||
test("SC7b: a too-short (< floor) title that is a literal prefix → none (floor guards false high)", () => {
|
||||
const graph = assemblePostGraph({ records: [rec(BODY)], analytics: [row("Jeg lærte")] });
|
||||
assert.equal(graph[0].match.confidence, "none");
|
||||
});
|
||||
|
||||
test("SC7c: a LinkedIn-truncated '…' title + same date → high (ellipsis stripped)", () => {
|
||||
const graph = assemblePostGraph({
|
||||
records: [rec(BODY)],
|
||||
analytics: [row("Jeg lærte noe viktig om dømmekraft i…")],
|
||||
});
|
||||
assert.equal(graph[0].match.confidence, "high");
|
||||
});
|
||||
|
||||
test("SC7d: prefix match but a different date → low (surfaced, not hidden)", () => {
|
||||
const graph = assemblePostGraph({
|
||||
records: [rec(BODY)],
|
||||
analytics: [row(FULL_TITLE, { publishedDate: "2026-05-20" })],
|
||||
});
|
||||
assert.equal(graph[0].match.confidence, "low");
|
||||
assert.ok(graph[0].match.row, "row still attached on a low match");
|
||||
});
|
||||
|
||||
// SC8 — pure + total.
|
||||
test("SC8: empty records → empty graph", () => {
|
||||
assert.deepEqual(assemblePostGraph({ records: [], analytics: [row(FULL_TITLE)] }), []);
|
||||
});
|
||||
|
||||
test("SC8: empty analytics → every post present with match none, no throw", () => {
|
||||
const graph = assemblePostGraph({ records: [rec(BODY), rec("another post body here")], analytics: [] });
|
||||
assert.equal(graph.length, 2);
|
||||
assert.ok(graph.every((g) => g.match.confidence === "none"));
|
||||
});
|
||||
|
||||
test("SC8: best-of prefers high over low and is deterministic", () => {
|
||||
const r = rec(BODY);
|
||||
const high = row(FULL_TITLE); // same date
|
||||
const low = row(FULL_TITLE, { publishedDate: "2026-01-01" }); // different date
|
||||
const graph = assemblePostGraph({ records: [r], analytics: [low, high] });
|
||||
assert.equal(graph[0].match.confidence, "high", "high beats low regardless of input order");
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue