feat(confidence-engine): preserve initial reconstruction relationships

This commit is contained in:
2026-09-05 11:53:58 +01:00
parent 13fbceee7a
commit 7472b6ecb0
7 changed files with 485 additions and 23 deletions
+42 -9
View File
@@ -22,6 +22,12 @@ export function buildInitialGraph(analysisData) {
}
const nodeMap = new Map(); // label -> node
const sourceIdToNodeId = new Map();
function mapSourceId(sourceId, node) {
if (sourceId) sourceIdToNodeId.set(sourceId, node.id);
return node;
}
// ── Helper: register or get a node by label ────────────
@@ -97,14 +103,17 @@ export function buildInitialGraph(analysisData) {
obs.confidence || "medium",
);
if (obs.id) node.evidenceIds.push(obs.id);
if (obs.id) {
node.evidenceIds.push(obs.id);
mapSourceId(obs.id, node);
}
}
}
// Actors as states/nodes
if (reconstruction.actors) {
for (const actor of reconstruction.actors) {
ensureNode(
mapSourceId(actor.id, ensureNode(
actor.description || actor.label,
"observation",
"supported",
@@ -112,13 +121,13 @@ export function buildInitialGraph(analysisData) {
null,
null,
actor.confidence || "medium",
);
));
}
}
if (reconstruction.systemsOrObjects) {
for (const sys of reconstruction.systemsOrObjects) {
ensureNode(
mapSourceId(sys.id, ensureNode(
sys.description || sys.label,
"metric",
"known",
@@ -126,7 +135,7 @@ export function buildInitialGraph(analysisData) {
null,
null,
sys.confidence || "medium",
);
));
}
}
@@ -142,6 +151,7 @@ export function buildInitialGraph(analysisData) {
null,
diff.confidence || "medium",
);
mapSourceId(diff.id, node);
}
}
@@ -157,6 +167,7 @@ export function buildInitialGraph(analysisData) {
null,
c.confidence || "medium",
);
mapSourceId(c.id, node);
}
}
@@ -173,6 +184,7 @@ export function buildInitialGraph(analysisData) {
null,
unk.confidence || "low",
);
mapSourceId(unk.id, node);
unknownNodes.push(node);
}
}
@@ -180,7 +192,7 @@ export function buildInitialGraph(analysisData) {
// Plausible interpretations
if (reconstruction.plausibleInterpretations) {
for (const interp of reconstruction.plausibleInterpretations) {
ensureNode(
mapSourceId(interp.id, ensureNode(
interp.description || interp.label,
"assumption",
"provisional",
@@ -188,14 +200,14 @@ export function buildInitialGraph(analysisData) {
null,
null,
interp.confidence || "low",
);
));
}
}
// Known transitions
if (reconstruction.knownTransitions) {
for (const trans of reconstruction.knownTransitions) {
ensureNode(
mapSourceId(trans.id, ensureNode(
`${trans.entity}: ${trans.previousState}${trans.currentState}`,
"transition",
trans.explanationStatus === "confirmed" ? "known" : "provisional",
@@ -204,7 +216,7 @@ export function buildInitialGraph(analysisData) {
null,
null,
trans.confidence || "medium",
);
));
}
}
@@ -247,6 +259,27 @@ export function buildInitialGraph(analysisData) {
}
}
const edgeIds = new Set(edges.map((edge) => edge.id));
for (const relationship of reconstruction.relationships ?? []) {
const fromNodeId = sourceIdToNodeId.get(relationship.fromId);
const toNodeId = sourceIdToNodeId.get(relationship.toId);
const id = `e-rel-${relationship.id}`;
if (!fromNodeId || !toNodeId || edgeIds.has(id)) continue;
edges.push(
situationEdgeSchema.parse({
id,
fromNodeId,
toNodeId,
relationship: relationship.relationship,
confidence: relationship.confidence,
description: relationship.description,
}),
);
edgeIds.add(id);
}
return { nodes: nodeArr, edges };
}
+21 -4
View File
@@ -7,14 +7,14 @@ const __dirname = dirname(__filename);
const PROMPTS_DIR = join(__dirname, "../../prompts");
/** Available prompt versions */
export const PROMPT_VERSIONS = ["v0.1", "v0.2", "v0.3", "v0.4"];
export const PROMPT_VERSIONS = ["v0.1", "v0.2", "v0.3", "v0.4", "v0.5"];
/** Default prompt version (override via RECONSTRUCTION_PROMPT_VERSION env var) */
const defaultVersionFromEnv = process.env.RECONSTRUCTION_PROMPT_VERSION;
export const DEFAULT_PROMPT_VERSION =
defaultVersionFromEnv && PROMPT_VERSIONS.includes(defaultVersionFromEnv)
? defaultVersionFromEnv
: "v0.4";
: "v0.5";
/** Build a v0.1 (extraction-only) prompt inline for backward compatibility */
function buildV1Prompt(scenario) {
@@ -91,15 +91,29 @@ async function buildV4Prompt(scenario) {
}
}
/** Load a versioned prompt from disk and substitute {{SCENARIO}} */
async function buildV5Prompt(scenario) {
try {
const content = await fs.readFile(
join(PROMPTS_DIR, "reconstruct-v0.5.md"),
"utf-8",
);
return content.replace("{{SCENARIO}}", scenario);
} catch {
// Fall back to v0.4 prompt if v0.5 file is missing
return buildV4Prompt(scenario);
}
}
/**
* Build an analysis prompt for the given version.
* @param {string} scenario - The scenario text
* @param {"v0.1" | "v0.2" | "v0.3" | "v0.4"} [version="v0.4"] - Prompt version
* @param {"v0.1" | "v0.2" | "v0.3" | "v0.4" | "v0.5"} [version="v0.5"] - Prompt version
* @param {object} [opts] - Optional experimental parameters
* @param {string} [opts.experimentInstruction] - Bounded experimental instruction block appended to the base prompt (production prompt is never replaced)
* @returns {Promise<{prompt: string, version: string}>}
*/
export async function buildPrompt(scenario, version = "v0.4", opts = {}) {
export async function buildPrompt(scenario, version = "v0.5", opts = {}) {
let prompt;
switch (version) {
case "v0.1":
@@ -111,6 +125,9 @@ export async function buildPrompt(scenario, version = "v0.4", opts = {}) {
case "v0.4":
prompt = await buildV4Prompt(scenario);
break;
case "v0.5":
prompt = await buildV5Prompt(scenario);
break;
default: // v0.3
prompt = await buildV3Prompt(scenario);
break;
+19
View File
@@ -126,6 +126,24 @@ const evidenceRecordSchema = z.object({
importance: importanceEnum,
});
const reconstructionRelationshipSchema = z.object({
id: z.string().min(1),
fromId: z.string().min(1),
toId: z.string().min(1),
relationship: z.enum([
"supports",
"weakens",
"contradicts",
"depends_on",
"causes",
"may_cause",
"compares_with",
"other",
]),
description: z.string().min(1),
confidence: confidenceEnum,
});
const reconstructionSchemaV2 = z.object({
summary: z.string().min(1),
actors: z.array(itemSchemaV1),
@@ -159,6 +177,7 @@ const reconstructionSchemaV2 = z.object({
confidence: confidenceEnum,
}),
),
relationships: z.array(reconstructionRelationshipSchema).optional().default([]),
});
const inputClassificationSchema = z.object({