fix: continue question selection after graph updates
This commit is contained in:
+127
-2
@@ -1407,15 +1407,24 @@ function buildDecompositionTemplates(parentNode, graph, depth = 0) {
|
||||
dependsOnLabels: [],
|
||||
},
|
||||
{
|
||||
label: "What happens when this problem is not resolved",
|
||||
label: "Whether other people experience this problem",
|
||||
description:
|
||||
"Need to know what happens when this problem is not resolved, because that is needed before judging whether the problem matters.",
|
||||
"Need to know whether other people experience this problem, because that must be established before deciding whether the problem is broadly important.",
|
||||
dependsOnLabels: ["Who experiences this problem"],
|
||||
},
|
||||
{
|
||||
label: "How often this problem happens",
|
||||
description:
|
||||
"Need to know how often this problem happens, because that helps judge whether it is a real recurring problem.",
|
||||
dependsOnLabels: [
|
||||
"Who experiences this problem",
|
||||
"Whether other people experience this problem",
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "What happens when this problem is not resolved",
|
||||
description:
|
||||
"Need to know what happens when this problem is not resolved, because that is needed before judging whether the problem matters.",
|
||||
dependsOnLabels: ["Who experiences this problem"],
|
||||
},
|
||||
{
|
||||
@@ -1424,6 +1433,7 @@ function buildDecompositionTemplates(parentNode, graph, depth = 0) {
|
||||
"Need to know how people deal with this problem today, because that is needed before comparing alternatives or value.",
|
||||
dependsOnLabels: [
|
||||
"Who experiences this problem",
|
||||
"Whether other people experience this problem",
|
||||
"What happens when this problem is not resolved",
|
||||
"How often this problem happens",
|
||||
],
|
||||
@@ -1435,6 +1445,7 @@ function buildDecompositionTemplates(parentNode, graph, depth = 0) {
|
||||
"Need to know whether people actively look for help with this problem, because that is needed before judging demand or willingness to pay.",
|
||||
dependsOnLabels: [
|
||||
"Who experiences this problem",
|
||||
"Whether other people experience this problem",
|
||||
"What happens when this problem is not resolved",
|
||||
"How often this problem happens",
|
||||
"How people deal with this problem today",
|
||||
@@ -1446,6 +1457,7 @@ function buildDecompositionTemplates(parentNode, graph, depth = 0) {
|
||||
"Need to know whether people would pay to solve this problem, because that can only be judged after the problem itself is established.",
|
||||
dependsOnLabels: [
|
||||
"Who experiences this problem",
|
||||
"Whether other people experience this problem",
|
||||
"What happens when this problem is not resolved",
|
||||
"How often this problem happens",
|
||||
"How people deal with this problem today",
|
||||
@@ -1635,6 +1647,81 @@ function isSelectableUnresolvedUnknown(graph, nodeId) {
|
||||
);
|
||||
}
|
||||
|
||||
function listUnresolvedUnknownCandidates(
|
||||
graph,
|
||||
resolvedCurrentTurnNodeIds = [],
|
||||
) {
|
||||
const resolvedCurrentTurnSet = new Set(resolvedCurrentTurnNodeIds || []);
|
||||
|
||||
return (graph.nodes || []).filter(
|
||||
(node) =>
|
||||
node.kind === "unknown" &&
|
||||
!["resolved", "contradicted"].includes(node.status) &&
|
||||
!(graph.resolvedNodeIds || []).includes(node.id) &&
|
||||
!resolvedCurrentTurnSet.has(node.id),
|
||||
);
|
||||
}
|
||||
|
||||
function listEligibleUnknownCandidates(graph, resolvedCurrentTurnNodeIds = []) {
|
||||
return listUnresolvedUnknownCandidates(
|
||||
graph,
|
||||
resolvedCurrentTurnNodeIds,
|
||||
).filter(
|
||||
(node) =>
|
||||
(scoreUnknownCandidate(graph, node, graph.resolvedNodeIds || [])
|
||||
.unresolvedParentUnknownCount ?? 0) === 0,
|
||||
);
|
||||
}
|
||||
|
||||
function selectOrderedSiblingCandidate(
|
||||
graph,
|
||||
candidateNodeIds = [],
|
||||
resolvedCurrentTurnNodeIds = [],
|
||||
) {
|
||||
const candidates = candidateNodeIds
|
||||
.map((nodeId) => findNodeById(graph, nodeId))
|
||||
.filter(Boolean);
|
||||
|
||||
if (candidates.length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const parentId = candidates[0]?.parentId ?? null;
|
||||
if (!parentId || !candidates.every((node) => node.parentId === parentId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const parentNode = findNodeById(graph, parentId);
|
||||
const orderedIds = (parentNode?.childIds || []).filter((nodeId) =>
|
||||
candidateNodeIds.includes(nodeId),
|
||||
);
|
||||
const fallbackOrderedIds = (graph.nodes || [])
|
||||
.filter((node) => candidateNodeIds.includes(node.id))
|
||||
.map((node) => node.id);
|
||||
const orderedCandidateIds =
|
||||
orderedIds.length > 0 ? orderedIds : fallbackOrderedIds;
|
||||
|
||||
const eligibleIds = new Set(
|
||||
listEligibleUnknownCandidates(graph, resolvedCurrentTurnNodeIds).map(
|
||||
(node) => node.id,
|
||||
),
|
||||
);
|
||||
|
||||
const selectedId = orderedCandidateIds.find((nodeId) =>
|
||||
eligibleIds.has(nodeId),
|
||||
);
|
||||
if (!selectedId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
status: "selected",
|
||||
nodeId: selectedId,
|
||||
reason:
|
||||
"Resolved a sibling tie using the deterministic decomposition order after the update left multiple equally scored follow-up children.",
|
||||
};
|
||||
}
|
||||
|
||||
function selectedQuestionBelongsToChild(graph, selectedQuestion) {
|
||||
if (!selectedQuestion?.nodeId) return false;
|
||||
return Boolean(findNodeById(graph, selectedQuestion.nodeId)?.parentId);
|
||||
@@ -2356,6 +2443,9 @@ export function applyValidatedProposal({
|
||||
reasoningResolution.reasoningStateOverride,
|
||||
);
|
||||
updatedSituationGraph.reasoningState = nextReasoningState;
|
||||
const resolvedCurrentTurnNodeIds = [
|
||||
...new Set(proposalSnapshot.resolvedUnknownNodeIds || []),
|
||||
];
|
||||
deterministicSelection = isSelectableUnresolvedUnknown(
|
||||
updatedSituationGraph,
|
||||
decompositionResult.selectedChildNodeId,
|
||||
@@ -2371,6 +2461,27 @@ export function applyValidatedProposal({
|
||||
updatedSituationGraph.resolvedNodeIds,
|
||||
);
|
||||
|
||||
if (deterministicSelection?.status === "ambiguous") {
|
||||
const orderedSiblingSelection = selectOrderedSiblingCandidate(
|
||||
updatedSituationGraph,
|
||||
deterministicSelection.tiedCandidateIds || [],
|
||||
resolvedCurrentTurnNodeIds,
|
||||
);
|
||||
|
||||
if (orderedSiblingSelection) {
|
||||
deterministicSelection = orderedSiblingSelection;
|
||||
}
|
||||
}
|
||||
|
||||
const unresolvedCandidates = listUnresolvedUnknownCandidates(
|
||||
updatedSituationGraph,
|
||||
resolvedCurrentTurnNodeIds,
|
||||
);
|
||||
const eligibleCandidates = listEligibleUnknownCandidates(
|
||||
updatedSituationGraph,
|
||||
resolvedCurrentTurnNodeIds,
|
||||
);
|
||||
|
||||
const atomicityAssessment = decompositionResult.atomicityAssessment;
|
||||
const answerabilityAssessment = decompositionResult.answerabilityAssessment;
|
||||
const decompositionDepth = decompositionResult.decompositionDepth;
|
||||
@@ -2500,6 +2611,15 @@ export function applyValidatedProposal({
|
||||
)
|
||||
? finalSelectedQuestion?.nodeId
|
||||
: null);
|
||||
const noQuestionReason = finalSelectedQuestion?.question
|
||||
? null
|
||||
: deterministicSelection?.status === "ambiguous"
|
||||
? "Eligible unresolved candidates remain tied after update-time reselection."
|
||||
: eligibleCandidates.length === 0
|
||||
? unresolvedCandidates.length === 0
|
||||
? "No unresolved unknown candidates remain after this update."
|
||||
: "Unresolved unknowns remain, but none are currently eligible for direct investigation."
|
||||
: "Question formulation did not produce a valid next question despite eligible unresolved candidates.";
|
||||
|
||||
const resultGraphValidation = situationGraphSchema.safeParse(
|
||||
updatedSituationGraph,
|
||||
@@ -2580,6 +2700,11 @@ export function applyValidatedProposal({
|
||||
decompositionResult.decompositionTriggeredByAnswerability ?? false,
|
||||
previousQuestion,
|
||||
finalQuestion: finalSelectedQuestion?.question ?? null,
|
||||
unresolvedCandidateCount: unresolvedCandidates.length,
|
||||
eligibleCandidateCount: eligibleCandidates.length,
|
||||
candidateNodeIds: eligibleCandidates.map((node) => node.id),
|
||||
resolvedCurrentTurnNodeIds,
|
||||
noQuestionReason,
|
||||
plainLanguageNormalisations,
|
||||
propagationPerformed,
|
||||
resolvedChildNodeId,
|
||||
|
||||
Reference in New Issue
Block a user