// Pure merge/enrichment functions (contracts.md A/B/C). Catalog lookups are injected // as a `getColumns` function parameter so these are testable with no `tht` shell-out. const test = require("node:test"); const assert = require("node:assert"); const { enrichCtePlanV2, buildCteResultV2, enrichPhaseSummaryV2, } = require("../core/enrich.js"); // --- enrichCtePlanV2 -------------------------------------------------------------- test("enrichCtePlanV2 assigns 1-based index in ctes[] order", () => { const data = { schema_version: 2, question: "q", strategy: "s", ctes: [ { name: "a", tables: [], filters: [] }, { name: "b", tables: [], filters: [] }, ], }; const out = enrichCtePlanV2(data, () => null); assert.equal(out.ctes[0].index, 1); assert.equal(out.ctes[1].index, 2); }); test("enrichCtePlanV2 fills tables[].description from the catalog", () => { const getColumns = (table) => table === "dim_patient" ? { description: "Anagrafica pazienti", columns: [{ name: "cod_paz", description: "Codice paziente" }] } : null; const data = { schema_version: 2, question: "q", strategy: "s", ctes: [{ name: "a", tables: [{ name: "dim_patient" }], filters: [] }], }; const out = enrichCtePlanV2(data, getColumns); assert.equal(out.ctes[0].tables[0].description, "Anagrafica pazienti"); }); test("enrichCtePlanV2 fills filters[].description by resolving table.column", () => { const getColumns = (table) => table === "dim_patient" ? { description: "Anagrafica", columns: [{ name: "flag_attivo", description: "Flag attivo" }] } : null; const data = { schema_version: 2, question: "q", strategy: "s", ctes: [{ name: "a", tables: [], filters: [{ column: "dim_patient.flag_attivo", op: "IS", value: "TRUE" }] }], }; const out = enrichCtePlanV2(data, getColumns); assert.equal(out.ctes[0].filters[0].description, "Flag attivo"); }); test("enrichCtePlanV2: catalog miss -> empty description string, never throws", () => { const out = enrichCtePlanV2( { schema_version: 2, question: "q", strategy: "s", ctes: [{ name: "a", tables: [{ name: "nonexistent_table" }], filters: [{ column: "nonexistent_table.x", op: "IS", value: "1" }] }], }, () => null, ); assert.equal(out.ctes[0].tables[0].description, ""); assert.equal(out.ctes[0].filters[0].description, ""); }); test("enrichCtePlanV2: getColumns throwing is tolerated by the caller contract (function itself does not catch)", () => { // enrich.js documents that getColumns must itself be safe (try/catch -> null on miss); // enrichCtePlanV2 does not need its own try/catch around calls, since getColumns never throws. const getColumns = () => null; // simulates the safe wrapper's miss behavior const out = enrichCtePlanV2( { schema_version: 2, question: "q", strategy: "s", ctes: [{ name: "a", tables: [{ name: "t" }], filters: [] }] }, getColumns, ); assert.equal(out.ctes[0].tables[0].description, ""); }); test("enrichCtePlanV2 preserves model-authored fields (purpose/rationale/depends_on/keys/output_columns)", () => { const data = { schema_version: 2, question: "q", strategy: "s", ctes: [{ name: "a", purpose: "scopo", rationale: "motivo", depends_on: [], keys: ["cod_paz"], output_columns: ["cod_paz"], tables: [], filters: [], }], }; const out = enrichCtePlanV2(data, () => null); assert.equal(out.ctes[0].purpose, "scopo"); assert.equal(out.ctes[0].rationale, "motivo"); assert.deepEqual(out.ctes[0].keys, ["cod_paz"]); assert.deepEqual(out.ctes[0].output_columns, ["cod_paz"]); }); // --- buildCteResultV2 -------------------------------------------------------------- test("buildCteResultV2 merges name/index/total/sql from cteInfo", () => { const thin = { schema_version: 2, note: "nota" }; const cteInfo = { name: "base_pazienti", index: 1, total: 3, sql: "WITH base_pazienti AS (SELECT 1)", doc: null, last_test: { status: "ok", execution_ms: 42, row_sample: 5, warnings: [], sql_hash: "h1", columns: ["x"], preview_rows: [[1]] }, }; const out = buildCteResultV2(thin, cteInfo); assert.equal(out.name, "base_pazienti"); assert.equal(out.index, 1); assert.equal(out.total, 3); assert.equal(out.sql, "WITH base_pazienti AS (SELECT 1)"); assert.equal(out.note, "nota"); }); test("buildCteResultV2 prefers doc purpose/rationale/depends_on, falls back to thin data", () => { const cteInfoWithDoc = { name: "a", index: 1, total: 1, sql: "WITH a AS (SELECT 1)", doc: { purpose: "scopo dal doc", rationale: "motivo dal doc", depends_on: ["x"] }, last_test: { status: "ok", execution_ms: 1, row_sample: 0, warnings: [], sql_hash: "h", columns: [], preview_rows: [] }, }; const out1 = buildCteResultV2({ schema_version: 2, purpose: "scopo thin" }, cteInfoWithDoc); assert.equal(out1.purpose, "scopo dal doc"); assert.equal(out1.rationale, "motivo dal doc"); assert.deepEqual(out1.depends_on, ["x"]); const cteInfoNoDoc = { ...cteInfoWithDoc, doc: null }; const out2 = buildCteResultV2({ schema_version: 2, purpose: "scopo thin", rationale: "motivo thin" }, cteInfoNoDoc); assert.equal(out2.purpose, "scopo thin"); assert.equal(out2.rationale, "motivo thin"); }); test("buildCteResultV2 builds preview from last_test.columns/preview_rows", () => { const cteInfo = { name: "a", index: 1, total: 1, sql: "WITH a AS (SELECT 1)", doc: null, last_test: { status: "ok", execution_ms: 10, row_sample: 2, warnings: [], sql_hash: "h1", columns: ["cod_paz", "eta"], preview_rows: [[1, 30], [2, 45]] }, }; const out = buildCteResultV2({ schema_version: 2 }, cteInfo); assert.deepEqual(out.preview, { columns: ["cod_paz", "eta"], rows: [[1, 30], [2, 45]] }); assert.equal(out.status, "ok"); assert.equal(out.execution_ms, 10); assert.equal(out.row_sample, 2); assert.equal(out.sql_hash, "h1"); assert.deepEqual(out.warnings, []); }); test("buildCteResultV2: last_test.preview_rows null (pre-upgrade ok records) -> preview.rows is []", () => { const cteInfo = { name: "a", index: 1, total: 1, sql: "WITH a AS (SELECT 1)", doc: null, last_test: { status: "ok", execution_ms: 1, row_sample: 1, warnings: [], sql_hash: "h", columns: ["cod_paz"], preview_rows: null }, }; const out = buildCteResultV2({ schema_version: 2 }, cteInfo); assert.deepEqual(out.preview.rows, []); }); test("buildCteResultV2: columns[] carries name, with description left as an empty placeholder (filled separately by the gate)", () => { const cteInfo = { name: "a", index: 1, total: 1, sql: "WITH a AS (SELECT 1)", doc: null, last_test: { status: "ok", execution_ms: 1, row_sample: 1, warnings: [], sql_hash: "h", columns: ["cod_paz"], preview_rows: [[1]] }, }; const out = buildCteResultV2({ schema_version: 2 }, cteInfo); assert.equal(out.columns.length, 1); assert.equal(out.columns[0].name, "cod_paz"); assert.equal(out.columns[0].description, ""); }); // --- enrichPhaseSummaryV2 ----------------------------------------------------------- test("enrichPhaseSummaryV2 fills phase from phaseMeta", () => { const data = { schema_version: 2, summary: "riepilogo" }; const phaseMeta = { id: "F5", num: 5, name: "sintesi" }; const out = enrichPhaseSummaryV2(data, phaseMeta, () => null); assert.deepEqual(out.phase, { id: "F5", num: 5, name: "sintesi" }); }); test("enrichPhaseSummaryV2 enriches tables[]/tables[].columns[]/sections[].items[] descriptions", () => { const getColumns = (table) => table === "dim_patient" ? { description: "Anagrafica", columns: [{ name: "cod_paz", description: "Codice paziente" }] } : null; const data = { schema_version: 2, summary: "s", tables: [{ name: "dim_patient", role: "promoted", columns: [{ name: "cod_paz" }] }], sections: [{ title: "Criteri", items: [{ label: "id paziente", table: "dim_patient", column: "cod_paz" }] }], }; const out = enrichPhaseSummaryV2(data, { id: "F5", num: 5, name: "sintesi" }, getColumns); assert.equal(out.tables[0].description, "Anagrafica"); assert.equal(out.tables[0].columns[0].description, "Codice paziente"); assert.equal(out.sections[0].items[0].description, "Codice paziente"); }); test("enrichPhaseSummaryV2: catalog miss -> empty description, never throws", () => { const data = { schema_version: 2, summary: "s", tables: [{ name: "nope", role: "excluded", columns: [{ name: "x" }] }], sections: [{ title: "t", items: [{ label: "l", table: "nope", column: "x" }] }], }; const out = enrichPhaseSummaryV2(data, { id: "F5", num: 5, name: "sintesi" }, () => null); assert.equal(out.tables[0].description, ""); assert.equal(out.tables[0].columns[0].description, ""); assert.equal(out.sections[0].items[0].description, ""); }); test("enrichPhaseSummaryV2 preserves open_questions/checks untouched", () => { const data = { schema_version: 2, summary: "s", checks: [{ label: "x", status: "ok" }], open_questions: ["domanda aperta"], }; const out = enrichPhaseSummaryV2(data, { id: "F1", num: 1, name: "chiarimento" }, () => null); assert.deepEqual(out.checks, [{ label: "x", status: "ok" }]); assert.deepEqual(out.open_questions, ["domanda aperta"]); }); // --- appendLedgerSection ----------------------------------------------------------- const { appendLedgerSection } = require("../core/enrich.js"); test("appendLedgerSection appends only the phase's substantive decisions", () => { const data = { schema_version: 2, summary: "s", sections: [{ title: "Criteri", items: [] }] }; const decisions = [ { seq: 1, type: "concept_clarified", subject: "ablazione", detail: "solo transcatetere", rationale: "r1" }, { seq: 2, type: "phase_approved", subject: "phase:1", detail: "", rationale: "" }, // meta: non in emits { seq: 3, type: "table_promoted", subject: "dim_patient", detail: "", rationale: "" }, // altra fase ]; const out = appendLedgerSection(data, decisions, ["concept_clarified", "ambiguity_open"]); assert.equal(out.sections.length, 2); const ledger = out.sections[1]; assert.equal(ledger.title, "Decisioni registrate in questa fase (dal ledger)"); assert.equal(ledger.items.length, 1); assert.equal(ledger.items[0].label, "concept_clarified: ablazione"); assert.equal(ledger.items[0].value, "solo transcatetere"); // input non mutato assert.equal(data.sections.length, 1); }); test("appendLedgerSection is a no-op without matching decisions or with empty ledger", () => { const data = { schema_version: 2, summary: "s" }; assert.equal(appendLedgerSection(data, [], ["concept_clarified"]), data); assert.equal(appendLedgerSection(data, [{ type: "sql_approved", subject: "x" }], ["concept_clarified"]), data); assert.equal(appendLedgerSection(data, undefined, undefined), data); });