feat: decompose non-answerable unknowns

This commit is contained in:
2026-08-03 09:53:28 +01:00
parent db994d7764
commit 42d4da3496
5 changed files with 477 additions and 12 deletions
+87
View File
@@ -569,6 +569,93 @@ function isDirectlyAnswerableObservationChildText(text) {
);
}
function countPrerequisiteConceptSignals(text) {
let count = 0;
if (/\bproblem\b/.test(text)) count += 1;
if (/\b(audience|customer|user|buyer|stakeholder|recipient)\b/.test(text))
count += 1;
if (/\b(demand|seek help|actively look for help)\b/.test(text)) count += 1;
if (/\b(pay|willingness to pay|price|pricing)\b/.test(text)) count += 1;
if (
/\b(compare|comparison|different from|alternatives|alternative|existing alternatives|existing tools|better than)\b/.test(
text,
)
)
count += 1;
if (/\b(value|viability|justified|business case|commercial)\b/.test(text))
count += 1;
if (/\b(feasibility|technical)\b/.test(text)) count += 1;
return count;
}
function countIndependentAnswerDimensions(node, graph) {
const text = normaliseText(`${node?.label || ""} ${node?.description || ""}`);
const relatedNodes = collectRelatedNodes(node, graph);
const unresolvedDependencies = relatedNodes.filter(
(relatedNode) =>
relatedNode.kind === "unknown" && relatedNode.status !== "resolved",
).length;
const conjunctionCount = (text.match(/\b(and|or)\b/g) || []).length;
const prerequisiteConceptCount = countPrerequisiteConceptSignals(text);
const implicitConclusion =
/\b(commercially justified|commercial justification|commercial viability|business case|customer value|market demand|product validation|technical feasibility)\b/.test(
text,
) ||
(/\bevidence that\b/.test(text) &&
/\b(problem|need|demand|audience|customer|user|alternatives|better than)\b/.test(
text,
)) ||
(/\bwhether\b/.test(text) &&
/\b(addresses|solve|solves|justifies|supports|demonstrates)\b/.test(
text,
) &&
/\b(problem|value|need|demand|audience|customer|user)\b/.test(text));
return {
prerequisiteConceptCount,
unresolvedDependencies,
conjunctionCount,
implicitConclusion,
multipleEvidenceDimensions:
prerequisiteConceptCount >= 2 || conjunctionCount >= 2,
};
}
export function assessUnknownAnswerability({ node, graph }) {
const text = normaliseText(`${node?.label || ""} ${node?.description || ""}`);
const dimensionSummary = countIndependentAnswerDimensions(node, graph);
const independentlyAnswerable =
!dimensionSummary.implicitConclusion &&
!dimensionSummary.multipleEvidenceDimensions &&
dimensionSummary.unresolvedDependencies === 0 &&
dimensionSummary.prerequisiteConceptCount <= 1;
if (independentlyAnswerable) {
return {
independentlyAnswerable: true,
reason:
"This unknown can be answered directly without first resolving several prerequisite investigations.",
prerequisiteConceptCount: dimensionSummary.prerequisiteConceptCount,
decompositionRequired: false,
};
}
return {
independentlyAnswerable: false,
reason: dimensionSummary.implicitConclusion
? "This unknown asks for a higher-level conclusion that depends on several smaller investigations."
: "This unknown still bundles multiple prerequisite evidence dimensions, so it should be decomposed before it becomes the selected question.",
prerequisiteConceptCount: Math.max(
dimensionSummary.prerequisiteConceptCount,
dimensionSummary.unresolvedDependencies,
dimensionSummary.conjunctionCount + 1,
),
decompositionRequired: true,
};
}
export function assessUnknownAtomicity({ node, graph }) {
const nodeText = normaliseText(
`${node?.label || ""} ${node?.description || ""}`,