exp(18): implement investigation state assessment layer

Implement the three-dimensional assessment (phase, progress, conversation
health) that sits between narrative and behaviour selection.

Key changes:
- lib/assessment/investigation-state-assessor.js: assessor module with
  countObservations, assessPhase, assessProgress, assessConversationHealth,
  assessInvestigationState — deterministic classifiers using known rules
- tests/investigation-state-assessor.test.js: 51 tests covering phase
  classification (orienting→concluding), progress thresholds, health
  conditions, confidence aggregation, edge cases, and observation counting
- lib/graph/orchestrator.js: integration calls passing correctly-shaped input
  to assessInvestigationState() at three call sites (~552, ~904, ~1013)

Design decisions encoded in this iteration:
- countObservations counts nodes with known/resolved status + high-confidence
  non-unknown non-state nodes (not just explicit observation-kind nodes)
- Phase uses seven values including cannot_determine for insufficient data
- Progress uses resolution ratio thresholds: accelerating (>0.6), steady
  (0.2-0.6), stalled (<0.2 with ≥1 resolved)
- Overall confidence = minimum across all three dimensions (conservative)

Also adds investigation-state-assessment-contract.md and updates
design-evolution-log, investigation-state-assessment.md (status header),
and investigation-turn-cycle.md (implementation status table).
This commit is contained in:
2026-08-05 17:52:18 +01:00
parent a0a76d6171
commit 1273861f0c
7 changed files with 1608 additions and 7 deletions
+45 -2
View File
@@ -18,6 +18,7 @@ import {
determineGraphBackedQuestion,
} from "./apply-proposal.js";
import { buildGraphUpdatePrompt } from "./prompt-builder.js";
import assessInvestigationState from "../assessment/investigation-state-assessor.js";
import {
buildReasoningState,
formulateQuestion,
@@ -548,6 +549,20 @@ export async function startCase(body) {
initialQuestionResult.graphReasoningIntegrity ?? null,
noQuestionReason: initialQuestionResult.noQuestionReason ?? null,
}),
assessment: assessInvestigationState({
situationGraph,
selectedQuestion,
noQuestionReason: initialQuestionResult.noQuestionReason ?? null,
diagnostics: {
promptVersion,
modelName: analysis?.modelName ?? null,
responseDurationMs: analysis?.responseDurationMs ?? null,
validationStatus: analysis?.validationStatus ?? "invalid",
nodeCount: situationGraph?.nodes?.length ?? 0,
edgeCount: situationGraph?.edges?.length ?? 0,
reasoningPattern: initialQuestionResult.selectedQuestion?.reasoningPattern ?? null,
},
}),
};
}
@@ -886,8 +901,22 @@ async function updateCaseWithDependencies(body, dependencies = {}) {
applicationResult.selectedQuestion,
),
}),
};
}
assessment: assessInvestigationState({
situationGraph: applicationResult.updatedSituationGraph,
selectedQuestion: applicationResult.selectedQuestion,
noQuestionReason: applicationResult.noQuestionReason ?? null,
diagnostics: {
promptVersion,
modelName,
responseDurationMs,
validationStatus: "valid",
nodeCount: applicationResult.updatedSituationGraph?.nodes?.length ?? 0,
edgeCount: applicationResult.updatedSituationGraph?.edges?.length ?? 0,
reasoningPattern: applicationResult.selectedQuestion?.reasoningPattern ?? null,
},
}),
};
}
return {
success: true,
@@ -981,5 +1010,19 @@ async function updateCaseWithDependencies(body, dependencies = {}) {
null,
),
}),
assessment: assessInvestigationState({
situationGraph,
selectedQuestion: null,
noQuestionReason: null,
diagnostics: {
promptVersion,
modelName,
responseDurationMs,
validationStatus: "valid",
nodeCount: situationGraph?.nodes?.length ?? 0,
edgeCount: situationGraph?.edges?.length ?? 0,
reasoningPattern: null,
},
}),
};
}