fix(reasoning): narrow decision audience question routing
This commit is contained in:
@@ -57,6 +57,31 @@ function collectResolvedContextValues(graph) {
|
|||||||
.filter((value) => typeof value === "string" && value.trim().length > 0);
|
.filter((value) => typeof value === "string" && value.trim().length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasAudienceIdentityQuestion(text) {
|
||||||
|
const normalised = normaliseText(text);
|
||||||
|
if (!normalised) return false;
|
||||||
|
|
||||||
|
return (
|
||||||
|
/\bwho\s+(is|are)\s+(the\s+)?(customer|user|buyer|stakeholder|recipient|audience)\b/.test(
|
||||||
|
normalised,
|
||||||
|
) ||
|
||||||
|
/\bwhich\s+(customer|user|buyer|stakeholder|recipient|audience|segment|segments)\b/.test(
|
||||||
|
normalised,
|
||||||
|
) ||
|
||||||
|
/\b(target|intended)\s+(customer|user|buyer|stakeholder|recipient|audience|segment)\b/.test(
|
||||||
|
normalised,
|
||||||
|
) ||
|
||||||
|
/\bidentify(ing)?\s+(the\s+)?(customer|user|buyer|stakeholder|recipient|audience|segment)\b/.test(
|
||||||
|
normalised,
|
||||||
|
) ||
|
||||||
|
/\bwho\s+experiences\b/.test(normalised) ||
|
||||||
|
/\bwho\s+(this|the decision|the product|the service)\s+is\s+for\b/.test(
|
||||||
|
normalised,
|
||||||
|
) ||
|
||||||
|
/\bwho\s+would\s+(receive|use|benefit)\b/.test(normalised)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function extractMeaning(node) {
|
function extractMeaning(node) {
|
||||||
const raw = `${node?.label || ""} ${node?.description || ""}`.trim();
|
const raw = `${node?.label || ""} ${node?.description || ""}`.trim();
|
||||||
let meaning = stripTrailingPunctuation(
|
let meaning = stripTrailingPunctuation(
|
||||||
@@ -64,12 +89,25 @@ function extractMeaning(node) {
|
|||||||
).trim();
|
).trim();
|
||||||
|
|
||||||
const lowered = normaliseText(raw);
|
const lowered = normaliseText(raw);
|
||||||
if (
|
if (hasAudienceIdentityQuestion(lowered)) {
|
||||||
/\b(customer|user|buyer|stakeholder|recipient|audience)\b/.test(lowered)
|
|
||||||
) {
|
|
||||||
return "the relevant customer, user, or value recipient";
|
return "the relevant customer, user, or value recipient";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const description = String(node?.description || "").trim();
|
||||||
|
const strippedDescription = stripTrailingPunctuation(description)
|
||||||
|
.replace(/^uncertainty regarding\s+/i, "")
|
||||||
|
.replace(/^uncertainty about\s+/i, "")
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
if (
|
||||||
|
/\b(status|likelihood|probability|chance|risk|uncertainty)\b/i.test(
|
||||||
|
String(node?.label || ""),
|
||||||
|
) &&
|
||||||
|
/^whether\s+/i.test(strippedDescription)
|
||||||
|
) {
|
||||||
|
return sentenceCase(strippedDescription);
|
||||||
|
}
|
||||||
|
|
||||||
meaning = meaning
|
meaning = meaning
|
||||||
.replace(/^uncertainty regarding\s+/i, "")
|
.replace(/^uncertainty regarding\s+/i, "")
|
||||||
.replace(/^uncertainty about\s+/i, "")
|
.replace(/^uncertainty about\s+/i, "")
|
||||||
@@ -123,7 +161,9 @@ function sanitizeQuestionText(question) {
|
|||||||
* relocation realistic in this situation?".
|
* relocation realistic in this situation?".
|
||||||
*/
|
*/
|
||||||
function isInterrogativeMeaning(meaning) {
|
function isInterrogativeMeaning(meaning) {
|
||||||
const trimmed = String(meaning || "").trim().toLowerCase();
|
const trimmed = String(meaning || "")
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
if (!trimmed) return false;
|
if (!trimmed) return false;
|
||||||
|
|
||||||
// Wh-questions (direct or indirect): who/what/where/when/how ...
|
// Wh-questions (direct or indirect): who/what/where/when/how ...
|
||||||
@@ -1159,11 +1199,7 @@ function selectQuestionFamily({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (reasoningPattern === "decision") {
|
if (reasoningPattern === "decision") {
|
||||||
if (
|
if (hasAudienceIdentityQuestion(text)) {
|
||||||
/\b(audience|customer|user|buyer|stakeholder|recipient|who experiences)\b/.test(
|
|
||||||
text,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return { family: "decision_foundation", template: "decision_audience" };
|
return { family: "decision_foundation", template: "decision_audience" };
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@@ -1743,7 +1779,9 @@ function validateFormulatedQuestion(question, meaning) {
|
|||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
!overlappingWord &&
|
!overlappingWord &&
|
||||||
!/\b(decision|evidence|constraint|customer|value|outcome)\b/i.test(trimmed)
|
!/\b(decision|evidence|constraint|customer|user|buyer|stakeholder|recipient|audience|value|outcome|who experiences)\b/i.test(
|
||||||
|
trimmed,
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -302,6 +302,146 @@ describe("formulateQuestion", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("60B.21 signing-status node does not route to decision_audience and preserves the proposition", () => {
|
||||||
|
const unknown = makeNode({
|
||||||
|
id: "n-customer-signing-status",
|
||||||
|
label: "Prospective enterprise customer signing status",
|
||||||
|
description:
|
||||||
|
"Uncertainty about whether the prospective enterprise customer will sign if we launch this year, so that its resolution is needed to decide which timing option provides superior net value.",
|
||||||
|
kind: "unknown",
|
||||||
|
status: "unknown",
|
||||||
|
confidence: "medium",
|
||||||
|
});
|
||||||
|
|
||||||
|
const graph = makeGraphFor(unknown, {
|
||||||
|
centralStatement:
|
||||||
|
"We need to decide which launch timing option provides superior net value.",
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = formulateQuestion({ node: unknown, graph });
|
||||||
|
|
||||||
|
expect(result.questionFamily).not.toBe("decision_foundation");
|
||||||
|
expect(result.selectedQuestionTemplate).not.toBe("decision_audience");
|
||||||
|
expect(result.question.toLowerCase()).toContain(
|
||||||
|
"whether the prospective enterprise customer will sign if we launch this year",
|
||||||
|
);
|
||||||
|
expect(result.question.toLowerCase()).not.toContain(
|
||||||
|
"relevant customer, user, or value recipient",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("legitimate audience identity unknown still routes to decision_audience", () => {
|
||||||
|
const unknown = makeNode({
|
||||||
|
id: "n-target-customer",
|
||||||
|
label: "Who is the target customer?",
|
||||||
|
description:
|
||||||
|
"Need to know who the target customer is before continuing development.",
|
||||||
|
kind: "unknown",
|
||||||
|
status: "unknown",
|
||||||
|
confidence: "medium",
|
||||||
|
});
|
||||||
|
|
||||||
|
const graph = makeGraphFor(unknown, {
|
||||||
|
centralStatement:
|
||||||
|
"Before investing more, we need to know whether continuing development is commercially justified.",
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = formulateQuestion({ node: unknown, graph });
|
||||||
|
|
||||||
|
expect(result.questionFamily).toBe("decision_foundation");
|
||||||
|
expect(result.selectedQuestionTemplate).toBe("decision_audience");
|
||||||
|
expect(result.question).toBe("Who experiences this problem?");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("customer as proposition subject does not imply audience family", () => {
|
||||||
|
const unknown = makeNode({
|
||||||
|
id: "n-customer-renewal",
|
||||||
|
label: "Customer renewal likelihood",
|
||||||
|
description:
|
||||||
|
"Uncertainty about whether the customer will renew next year, because that affects the decision.",
|
||||||
|
kind: "unknown",
|
||||||
|
status: "unknown",
|
||||||
|
confidence: "medium",
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = formulateQuestion({
|
||||||
|
node: unknown,
|
||||||
|
graph: makeGraphFor(unknown),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.selectedQuestionTemplate).not.toBe("decision_audience");
|
||||||
|
expect(result.question.toLowerCase()).toContain(
|
||||||
|
"whether the customer will renew next year",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("user as proposition subject does not imply audience family", () => {
|
||||||
|
const unknown = makeNode({
|
||||||
|
id: "n-user-adoption",
|
||||||
|
label: "User adoption uncertainty",
|
||||||
|
description:
|
||||||
|
"Uncertainty about whether users will adopt the change, because that affects the decision.",
|
||||||
|
kind: "unknown",
|
||||||
|
status: "unknown",
|
||||||
|
confidence: "medium",
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = formulateQuestion({
|
||||||
|
node: unknown,
|
||||||
|
graph: makeGraphFor(unknown),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.selectedQuestionTemplate).not.toBe("decision_audience");
|
||||||
|
expect(result.question.toLowerCase()).toContain(
|
||||||
|
"whether users will adopt the change",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("buyer or stakeholder lexical mentions do not automatically trigger audience discovery", () => {
|
||||||
|
const unknown = makeNode({
|
||||||
|
id: "n-stakeholder-approval",
|
||||||
|
label: "Stakeholder approval uncertainty",
|
||||||
|
description:
|
||||||
|
"Uncertainty about whether the stakeholder will approve the plan, because that affects the decision.",
|
||||||
|
kind: "unknown",
|
||||||
|
status: "unknown",
|
||||||
|
confidence: "medium",
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = formulateQuestion({
|
||||||
|
node: unknown,
|
||||||
|
graph: makeGraphFor(unknown),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.selectedQuestionTemplate).not.toBe("decision_audience");
|
||||||
|
expect(result.question.toLowerCase()).toContain(
|
||||||
|
"whether the stakeholder will approve the plan",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("existing direct interrogative decision behaviour is preserved", () => {
|
||||||
|
const unknown = makeNode({
|
||||||
|
id: "n-client-leave",
|
||||||
|
label: "Will our largest client leave if we relocate?",
|
||||||
|
description:
|
||||||
|
"Uncertainty regarding whether our largest client would depart following a relocation to Manchester; matters because their departure would cost approximately £5M per year.",
|
||||||
|
kind: "unknown",
|
||||||
|
status: "unknown",
|
||||||
|
confidence: "medium",
|
||||||
|
});
|
||||||
|
|
||||||
|
const graph = makeGraphFor(unknown, {
|
||||||
|
centralStatement:
|
||||||
|
"We need to decide whether relocating is commercially justified.",
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = formulateQuestion({ node: unknown, graph });
|
||||||
|
|
||||||
|
expect(result.question.toLowerCase()).toBe(
|
||||||
|
"will our largest client leave if we relocate?",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("evidence-resolvable competing-cause unknown stays on an evidence route rather than neutral clarification", () => {
|
it("evidence-resolvable competing-cause unknown stays on an evidence route rather than neutral clarification", () => {
|
||||||
const unknown = makeNode({
|
const unknown = makeNode({
|
||||||
id: "n-delivery-cause",
|
id: "n-delivery-cause",
|
||||||
|
|||||||
Reference in New Issue
Block a user