feat(confidence-engine): deriveInvestigationLearning deterministic learning projection from canonical provenance
Adds deriveInvestigationLearning to episode-preparation.js: pure function that maps resolvedNodeIds + Contributions + Findings to investigation learning propositions, preserving node.label question text and contribution-provenance ordering. Owning tests prove: correct provenance, cross-question isolation, eligibility rule (null/agree eligible; not_quite/not_relevant excluded), unresolved exclusion, no-eligible-material omission, text preservation, and provenance authority over originatingTargetNodeId shortcut.
This commit is contained in:
@@ -15,9 +15,9 @@
|
||||
|
||||
// ── Eligibility constants ──────────────────────────────────
|
||||
|
||||
const ELIGIBLE_DISPOSITIONS = new Set([null, "agree"]);
|
||||
export const ELIGIBLE_DISPOSITIONS = new Set([null, "agree"]);
|
||||
|
||||
const EXCLUDED_DISPOSITIONS = new Set(["not_relevant", "not_quite"]);
|
||||
export const EXCLUDED_DISPOSITIONS = new Set(["not_relevant", "not_quite"]);
|
||||
|
||||
// ── Public API ─────────────────────────────────────────────
|
||||
|
||||
@@ -104,3 +104,79 @@ export function prepareCompletedEpisode({ situationGraph, targetNodeId, contribu
|
||||
excludedFindingProvenance,
|
||||
};
|
||||
}
|
||||
|
||||
// ── Investigation learning projection ────────────────────────
|
||||
|
||||
/**
|
||||
* Derive deterministic learning from a completed investigation episode.
|
||||
*
|
||||
* @param {Object} params
|
||||
* @param {Object} params.situationGraph - Authoritative SituationGraph (read-only)
|
||||
* @param {Array<Object>} params.contributions - All persisted Contributions
|
||||
* @param {Array<Object>} params.findings - All canonical Findings
|
||||
* @returns {Array<Object>} investigationLearning — ordered by resolvedNodeIds, then Contribution.sequence
|
||||
*/
|
||||
export function deriveInvestigationLearning({ situationGraph, contributions = [], findings = [] }) {
|
||||
const resolvedNodeIds = situationGraph?.resolvedNodeIds ?? [];
|
||||
|
||||
// Build lookup from node id to node (for question text)
|
||||
const nodeById = new Map();
|
||||
for (const node of situationGraph?.nodes ?? []) {
|
||||
if (node?.id) {
|
||||
nodeById.set(node.id, node);
|
||||
}
|
||||
}
|
||||
|
||||
const result = [];
|
||||
|
||||
for (const nodeId of resolvedNodeIds) {
|
||||
const node = nodeById.get(nodeId);
|
||||
if (!node) continue;
|
||||
|
||||
// Collect scoped contributions ordered by sequence
|
||||
const scopedContribs = [];
|
||||
for (const contrib of contributions) {
|
||||
if (contrib?.targetNodeId === nodeId) {
|
||||
scopedContribs.push(contrib);
|
||||
}
|
||||
}
|
||||
|
||||
const orderedContribs = scopedContribs
|
||||
.slice()
|
||||
.sort(function(a, b) {
|
||||
var aSeq = a?.sequence != null ? a.sequence : Infinity;
|
||||
var bSeq = b?.sequence != null ? b.sequence : Infinity;
|
||||
return aSeq - bSeq;
|
||||
});
|
||||
|
||||
// Build contribution ID set for provenance chain
|
||||
const contribIds = new Set(orderedContribs.map(function(c) { return c.id; }));
|
||||
|
||||
// Collect eligible findings from scoped contributions via Contribution provenance only
|
||||
var propositions = [];
|
||||
for (var i = 0; i < orderedContribs.length; i++) {
|
||||
var contrib = orderedContribs[i];
|
||||
for (var j = 0; j < findings.length; j++) {
|
||||
var finding = findings[j];
|
||||
if (finding?.contributionId === contrib.id && ELIGIBLE_DISPOSITIONS.has(finding?.userDisposition ?? null)) {
|
||||
propositions.push({
|
||||
findingId: finding.id ?? null,
|
||||
proposition: finding.proposition ?? "",
|
||||
userDisposition: finding.userDisposition ?? null,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only include questions that produced at least one eligible proposition
|
||||
if (propositions.length > 0) {
|
||||
result.push({
|
||||
nodeId: nodeId,
|
||||
questionText: node.label ?? "",
|
||||
propositions: propositions,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user