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
95 lines
3.9 KiB
TypeScript
95 lines
3.9 KiB
TypeScript
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");
|
|
});
|
|
});
|