Укрепить debt-boundary ответы MCP discovery
This commit is contained in:
parent
ee98b04706
commit
fa1731a5b4
|
|
@ -4317,11 +4317,10 @@ class AddressQueryService {
|
|||
rowsMatched: expandedFilteredRows.length
|
||||
});
|
||||
const expandedFactual = (0, composeStage_1.composeFactualReply)(intent.intent, expandedFilteredRows, composeRuntimeOptions(expandedLimitFilters));
|
||||
const expandedPrefix = `Период сохранен. Глубина live-выборки автоматически расширена до ${expandedPlan.limit} строк.`;
|
||||
const expandedLimitations = [...filters.warnings, "query_limit_auto_expanded_for_anchor_recovery"];
|
||||
const expandedReasons = [...baseReasons, "query_limit_auto_expanded_for_anchor_recovery"];
|
||||
return buildFactualExecutionResult({
|
||||
replyText: `${expandedPrefix}\n${expandedFactual.text}`,
|
||||
replyText: expandedFactual.text,
|
||||
responseType: expandedFactual.responseType,
|
||||
responseSemantics: expandedFactual.semantics,
|
||||
selectedRecipe: expandedSelection.selected_recipe.recipe_id,
|
||||
|
|
|
|||
|
|
@ -582,7 +582,8 @@ function runAssistantAddressLaneResponseRuntime(input) {
|
|||
mcp_discovery_response_candidate_v1: mcpDiscoveryResponsePolicy.candidate,
|
||||
mcp_discovery_response_applied: mcpDiscoveryResponsePolicy.applied,
|
||||
comparison_scope_response_augmentation_v1: comparisonScopeProofReply?.audit ?? null,
|
||||
...buildAppliedMcpDiscoveryRoutePatch(debugWithResponseGuard, mcpDiscoveryResponsePolicy.applied)
|
||||
...buildAppliedMcpDiscoveryRoutePatch(debugWithResponseGuard, mcpDiscoveryResponsePolicy.applied),
|
||||
...(0, assistantMcpDiscoveryResponsePolicy_1.buildAppliedMcpDiscoveryTruthPatch)(mcpDiscoveryResponsePolicy, debugWithResponseGuard)
|
||||
};
|
||||
const finalization = finalizeAddressTurnSafe({
|
||||
sessionId: input.sessionId,
|
||||
|
|
|
|||
|
|
@ -142,7 +142,8 @@ function runAssistantDeepTurnResponseRuntime(input) {
|
|||
...packagingRuntime.debug,
|
||||
mcp_discovery_response_policy_v1: mcpDiscoveryResponsePolicy,
|
||||
mcp_discovery_response_candidate_v1: mcpDiscoveryResponsePolicy.candidate,
|
||||
mcp_discovery_response_applied: mcpDiscoveryResponsePolicy.applied
|
||||
mcp_discovery_response_applied: mcpDiscoveryResponsePolicy.applied,
|
||||
...(0, assistantMcpDiscoveryResponsePolicy_1.buildAppliedMcpDiscoveryTruthPatch)(mcpDiscoveryResponsePolicy, packagingRuntime.debug)
|
||||
};
|
||||
const assistantItem = mcpDiscoveryResponsePolicy.applied
|
||||
? {
|
||||
|
|
|
|||
|
|
@ -362,6 +362,7 @@ async function runAssistantLivingChatRuntime(input) {
|
|||
mcp_discovery_response_policy_v1: mcpDiscoveryResponsePolicy,
|
||||
mcp_discovery_response_candidate_v1: mcpDiscoveryResponsePolicy.candidate,
|
||||
mcp_discovery_response_applied: mcpDiscoveryResponsePolicy.applied,
|
||||
...(0, assistantMcpDiscoveryResponsePolicy_1.buildAppliedMcpDiscoveryTruthPatch)(mcpDiscoveryResponsePolicy, addressRuntimeMeta),
|
||||
living_chat_script_guard_applied: livingChatScriptGuardApplied,
|
||||
living_chat_script_guard_reason: livingChatScriptGuardReason,
|
||||
living_chat_grounding_guard_applied: livingChatGroundingGuardApplied,
|
||||
|
|
|
|||
|
|
@ -73,6 +73,18 @@ function requestsBusinessOverviewNextChecks(turnMeaning, graph) {
|
|||
const text = normalizedTurnAndGraphText(turnMeaning, graph);
|
||||
return /(?:что\s+(?:дальше\s+)?провер\w*|что\s+провер\w*\s+дальше|следующ\w*\s+шаг|в\s+первую\s+очередь\s+провер\w*|next\s+checks?|next\s+steps?)/iu.test(text);
|
||||
}
|
||||
function requestsShortDebtBoundaryFollowup(turnMeaning, graph) {
|
||||
const text = normalizeQuestionText([
|
||||
turnMeaning?.raw_message,
|
||||
turnMeaning?.effective_message,
|
||||
graph?.source_message,
|
||||
graph?.question
|
||||
].join(" "));
|
||||
if (!/\u043f\u0440\u043e\u0441\u0440\u043e\u0447/iu.test(text)) {
|
||||
return false;
|
||||
}
|
||||
return /(?:\u043a\u043e\u0440\u043e\u0442\u043a|\u043f\u043e\u0447\u0435\u043c\u0443|\u0434\u043e\u043a\u0430\u0437\p{L}*)/iu.test(text);
|
||||
}
|
||||
function requestsCompactCashflowAnswer(turnMeaning, graph) {
|
||||
const text = normalizeQuestionText([
|
||||
turnMeaning?.raw_message,
|
||||
|
|
@ -902,6 +914,7 @@ function buildCompactBusinessOverviewReply(entryPoint, draft) {
|
|||
const debtDueDateBoundary = actionFamily === "debt_due_date_boundary" || unsupportedFamily === "debt_due_date_boundary";
|
||||
const vendorRiskBoundary = actionFamily === "vendor_risk_procurement_boundary" || unsupportedFamily === "vendor_risk_procurement_boundary";
|
||||
const inventoryReserveBoundary = actionFamily === "inventory_reserve_boundary" || unsupportedFamily === "inventory_reserve_liquidation_boundary";
|
||||
const shortDebtBoundaryFollowup = debtDueDateBoundary && requestsShortDebtBoundaryFollowup(turnMeaning, graph);
|
||||
const compactCashflowRequested = directMoneyOnlyAnswer && requestsCompactCashflowAnswer(turnMeaning, graph);
|
||||
const cashflowPolarityRequested = compactCashflowRequested && requestsCashflowPolarityAnswer(turnMeaning, graph);
|
||||
const directAccountingProfitRequested = requestsDirectAccountingProfitAnswer(turnMeaning, graph);
|
||||
|
|
@ -1035,19 +1048,20 @@ function buildCompactBusinessOverviewReply(entryPoint, draft) {
|
|||
const rowsWithPaymentTerms = typeof dueDateAging.rows_with_payment_terms === "number" && Number.isFinite(dueDateAging.rows_with_payment_terms)
|
||||
? dueDateAging.rows_with_payment_terms
|
||||
: null;
|
||||
const rowsWithAmount = typeof dueDateAging.rows_with_amount === "number" && Number.isFinite(dueDateAging.rows_with_amount)
|
||||
? dueDateAging.rows_with_amount
|
||||
: null;
|
||||
const dueDateScopePrefix = organizationScope ? `по компании ${organizationScope} ` : "";
|
||||
if (status === "confirmed_overdue") {
|
||||
lines.push(`Коротко: ${dueDateScopePrefix}на ${asOfDate} подтвержденная просрочка есть: ${overdueAmount ?? "сумма не распознана"} по ${dueDateAging.overdue_rows ?? "найденным"} строкам.`);
|
||||
lines.push("Основа ответа: открытые расчеты 60/62/76, договорный срок оплаты и дата расчетного документа; это проверка просрочки по срокам оплаты, а не просто возраст договора.");
|
||||
}
|
||||
else if (status === "no_payment_terms_configured") {
|
||||
if (shortDebtBoundaryFollowup) {
|
||||
lines.push(`Коротко: да, доказать просрочку ${dueDateScopePrefix}на ${asOfDate} нельзя, потому что в подтвержденных данных не установлен срок оплаты.`);
|
||||
lines.push(`Открытый долг${grossAmount ? ` на ${grossAmount}` : ""} виден, но без due date это задолженность без доказанной договорной просрочки.`);
|
||||
return joinBusinessReplyLines(lines);
|
||||
}
|
||||
lines.push(`Коротко: ${dueDateScopePrefix}на ${asOfDate} подтвержденной просрочки нет: открытые расчеты проверены${grossAmount ? ` на ${grossAmount}` : ""}, но в найденных договорах срок оплаты не установлен.`);
|
||||
lines.push(rowsWithAmount !== null
|
||||
? `Проверено строк с суммой: ${rowsWithAmount}. Без установленного срока оплаты нельзя честно назвать эти остатки просрочкой.`
|
||||
: "Без установленного срока оплаты нельзя честно назвать эти остатки просрочкой.");
|
||||
lines.push("Без установленного срока оплаты нельзя честно назвать эти остатки просрочкой.");
|
||||
lines.push("Что проверить дальше: сроки оплаты и даты документов-оснований в договорах/расчетах; без них просрочку доказать нельзя.");
|
||||
}
|
||||
else if (status === "insufficient_due_date_basis") {
|
||||
lines.push(`Коротко: ${dueDateScopePrefix}на ${asOfDate} просрочка не подтверждена: по строкам с установленным сроком оплаты не хватило даты расчетного документа.`);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,111 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ASSISTANT_MCP_DISCOVERY_RESPONSE_POLICY_SCHEMA_VERSION = void 0;
|
||||
exports.buildAppliedMcpDiscoveryTruthPatch = buildAppliedMcpDiscoveryTruthPatch;
|
||||
exports.applyAssistantMcpDiscoveryResponsePolicy = applyAssistantMcpDiscoveryResponsePolicy;
|
||||
const assistantMcpDiscoveryResponseCandidate_1 = require("./assistantMcpDiscoveryResponseCandidate");
|
||||
const assistantRuntimeContracts_1 = require("../types/assistantRuntimeContracts");
|
||||
exports.ASSISTANT_MCP_DISCOVERY_RESPONSE_POLICY_SCHEMA_VERSION = "assistant_mcp_discovery_response_policy_v1";
|
||||
function uniqueReasonCodes(values) {
|
||||
const result = [];
|
||||
values.forEach((value) => {
|
||||
const text = toNonEmptyString(value);
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
const normalized = normalizeReasonCode(text);
|
||||
if (normalized && !result.includes(normalized)) {
|
||||
result.push(normalized);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
function patchCoverageGateState(value, gate) {
|
||||
const source = toRecordObject(value);
|
||||
if (!source) {
|
||||
return value;
|
||||
}
|
||||
const currentGate = toRecordObject(source.coverage_gate_state);
|
||||
return {
|
||||
...source,
|
||||
coverage_gate_state: {
|
||||
...(currentGate ?? {}),
|
||||
coverage_status: gate.coverage_status,
|
||||
evidence_grade: gate.evidence_grade,
|
||||
grounding_status: gate.grounding_status,
|
||||
truth_mode: gate.truth_mode,
|
||||
carryover_eligibility: gate.carryover_eligibility,
|
||||
reason_codes: gate.reason_codes
|
||||
}
|
||||
};
|
||||
}
|
||||
function buildAppliedMcpDiscoveryTruthPatch(policy, currentDebug) {
|
||||
if (!policy.applied || !ALLOWED_CANDIDATE_STATUSES.has(policy.candidate.candidate_status)) {
|
||||
return {};
|
||||
}
|
||||
const reasonCodes = uniqueReasonCodes([
|
||||
...policy.reason_codes,
|
||||
"mcp_discovery_response_policy_candidate_applied",
|
||||
"mcp_discovery_response_candidate_guarded_truth_patch"
|
||||
]);
|
||||
const truthGate = {
|
||||
schema_version: assistantRuntimeContracts_1.ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION,
|
||||
policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter",
|
||||
coverage_status: "partial",
|
||||
evidence_grade: "medium",
|
||||
grounding_status: "partial",
|
||||
truth_mode: "limited",
|
||||
carryover_eligibility: "root_only",
|
||||
reason_codes: reasonCodes,
|
||||
source_truth_gate_status: "partial_supported",
|
||||
blocked_or_limited_explanation: "mcp_discovery_guarded_bounded_answer"
|
||||
};
|
||||
const answerShape = {
|
||||
schema_version: assistantRuntimeContracts_1.ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION,
|
||||
policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter",
|
||||
answer_shape: "limited_with_reason",
|
||||
reply_type: "partial_coverage",
|
||||
capability_contract_id: null,
|
||||
transition_contract_id: null,
|
||||
may_state_confirmed_facts: true,
|
||||
must_include_limitation: true,
|
||||
may_power_followup: true,
|
||||
required_sections: ["direct_answer", "evidence_window", "limitations"],
|
||||
downgrade_only: true
|
||||
};
|
||||
const truthPolicy = {
|
||||
schema_version: assistantRuntimeContracts_1.ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION,
|
||||
policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter",
|
||||
truth_gate: truthGate,
|
||||
answer_shape: answerShape
|
||||
};
|
||||
const debug = currentDebug ?? {};
|
||||
const patch = {
|
||||
assistant_truth_answer_policy_v1: truthPolicy,
|
||||
coverage_gate_contract: truthGate,
|
||||
answer_shape_contract: answerShape,
|
||||
truth_gate_contract_status: "partial_supported",
|
||||
truth_mode: "limited",
|
||||
carryover_eligibility: "root_only",
|
||||
answer_shape: "limited_with_reason"
|
||||
};
|
||||
const stateTransition = patchCoverageGateState(debug.assistant_state_transition_v1, truthGate);
|
||||
if (stateTransition !== debug.assistant_state_transition_v1) {
|
||||
patch.assistant_state_transition_v1 = stateTransition;
|
||||
}
|
||||
const stateTransitionContract = patchCoverageGateState(debug.state_transition_contract, truthGate);
|
||||
if (stateTransitionContract !== debug.state_transition_contract) {
|
||||
patch.state_transition_contract = stateTransitionContract;
|
||||
}
|
||||
const runtimeContract = toRecordObject(debug.assistant_runtime_contract_v1);
|
||||
if (runtimeContract) {
|
||||
patch.assistant_runtime_contract_v1 = {
|
||||
...runtimeContract,
|
||||
truth_gate_contract_status: "partial_supported"
|
||||
};
|
||||
}
|
||||
return patch;
|
||||
}
|
||||
const ALLOWED_CANDIDATE_STATUSES = new Set([
|
||||
"ready_for_guarded_use",
|
||||
"checked_sources_only_candidate",
|
||||
|
|
|
|||
|
|
@ -5278,11 +5278,10 @@ export class AddressQueryService {
|
|||
expandedFilteredRows,
|
||||
composeRuntimeOptions(expandedLimitFilters)
|
||||
);
|
||||
const expandedPrefix = `Период сохранен. Глубина live-выборки автоматически расширена до ${expandedPlan.limit} строк.`;
|
||||
const expandedLimitations = [...filters.warnings, "query_limit_auto_expanded_for_anchor_recovery"];
|
||||
const expandedReasons = [...baseReasons, "query_limit_auto_expanded_for_anchor_recovery"];
|
||||
return buildFactualExecutionResult({
|
||||
replyText: `${expandedPrefix}\n${expandedFactual.text}`,
|
||||
replyText: expandedFactual.text,
|
||||
responseType: expandedFactual.responseType,
|
||||
responseSemantics: expandedFactual.semantics,
|
||||
selectedRecipe: expandedSelection.selected_recipe.recipe_id,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,10 @@ import {
|
|||
import { applyAssistantCapabilityBindingResponseGuard } from "./assistantCapabilityBindingResponseGuard";
|
||||
import { attachAssistantCapabilityRuntimeBinding } from "./assistantCapabilityRuntimeBindingAdapter";
|
||||
import { attachAssistantMcpDiscoveryDebug } from "./assistantMcpDiscoveryDebugAttachment";
|
||||
import { applyAssistantMcpDiscoveryResponsePolicy } from "./assistantMcpDiscoveryResponsePolicy";
|
||||
import {
|
||||
applyAssistantMcpDiscoveryResponsePolicy,
|
||||
buildAppliedMcpDiscoveryTruthPatch
|
||||
} from "./assistantMcpDiscoveryResponsePolicy";
|
||||
import { attachAssistantRuntimeContractShadow } from "./assistantRuntimeContractResolver";
|
||||
import { attachAssistantStateTransition } from "./assistantStateTransitionRuntimeAdapter";
|
||||
import { attachAssistantTruthAnswerPolicy } from "./assistantTruthAnswerPolicyRuntimeAdapter";
|
||||
|
|
@ -712,7 +715,8 @@ export function runAssistantAddressLaneResponseRuntime<ResponseType = AssistantM
|
|||
mcp_discovery_response_candidate_v1: mcpDiscoveryResponsePolicy.candidate,
|
||||
mcp_discovery_response_applied: mcpDiscoveryResponsePolicy.applied,
|
||||
comparison_scope_response_augmentation_v1: comparisonScopeProofReply?.audit ?? null,
|
||||
...buildAppliedMcpDiscoveryRoutePatch(debugWithResponseGuard, mcpDiscoveryResponsePolicy.applied)
|
||||
...buildAppliedMcpDiscoveryRoutePatch(debugWithResponseGuard, mcpDiscoveryResponsePolicy.applied),
|
||||
...buildAppliedMcpDiscoveryTruthPatch(mcpDiscoveryResponsePolicy, debugWithResponseGuard)
|
||||
};
|
||||
const finalization = finalizeAddressTurnSafe({
|
||||
sessionId: input.sessionId,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,10 @@ import {
|
|||
finalizeAssistantDeepTurn,
|
||||
type FinalizeAssistantDeepTurnInput
|
||||
} from "./assistantDeepTurnFinalizeRuntimeAdapter";
|
||||
import { applyAssistantMcpDiscoveryResponsePolicy } from "./assistantMcpDiscoveryResponsePolicy";
|
||||
import {
|
||||
applyAssistantMcpDiscoveryResponsePolicy,
|
||||
buildAppliedMcpDiscoveryTruthPatch
|
||||
} from "./assistantMcpDiscoveryResponsePolicy";
|
||||
import type { ClaimBoundAnchorAudit, TargetedEvidenceAcquisitionAudit } from "./assistantClaimBoundEvidence";
|
||||
import type { CompanyAnchorSet } from "./companyAnchorResolver";
|
||||
import type {
|
||||
|
|
@ -268,7 +271,11 @@ export function runAssistantDeepTurnResponseRuntime(
|
|||
...packagingRuntime.debug,
|
||||
mcp_discovery_response_policy_v1: mcpDiscoveryResponsePolicy,
|
||||
mcp_discovery_response_candidate_v1: mcpDiscoveryResponsePolicy.candidate,
|
||||
mcp_discovery_response_applied: mcpDiscoveryResponsePolicy.applied
|
||||
mcp_discovery_response_applied: mcpDiscoveryResponsePolicy.applied,
|
||||
...buildAppliedMcpDiscoveryTruthPatch(
|
||||
mcpDiscoveryResponsePolicy,
|
||||
packagingRuntime.debug as unknown as Record<string, unknown>
|
||||
)
|
||||
} as AssistantDebugPayload;
|
||||
const assistantItem = mcpDiscoveryResponsePolicy.applied
|
||||
? {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ import {
|
|||
} from "./assistantMemoryRecapPolicy";
|
||||
import { resolveAssistantOrganizationAuthority } from "./assistantContinuityPolicy";
|
||||
import { attachAssistantMcpDiscoveryDebug } from "./assistantMcpDiscoveryDebugAttachment";
|
||||
import { applyAssistantMcpDiscoveryResponsePolicy } from "./assistantMcpDiscoveryResponsePolicy";
|
||||
import {
|
||||
applyAssistantMcpDiscoveryResponsePolicy,
|
||||
buildAppliedMcpDiscoveryTruthPatch
|
||||
} from "./assistantMcpDiscoveryResponsePolicy";
|
||||
|
||||
export interface AssistantLivingChatSessionScopeInput {
|
||||
knownOrganizations?: unknown[];
|
||||
|
|
@ -473,6 +476,7 @@ export async function runAssistantLivingChatRuntime(
|
|||
mcp_discovery_response_policy_v1: mcpDiscoveryResponsePolicy,
|
||||
mcp_discovery_response_candidate_v1: mcpDiscoveryResponsePolicy.candidate,
|
||||
mcp_discovery_response_applied: mcpDiscoveryResponsePolicy.applied,
|
||||
...buildAppliedMcpDiscoveryTruthPatch(mcpDiscoveryResponsePolicy, addressRuntimeMeta),
|
||||
living_chat_script_guard_applied: livingChatScriptGuardApplied,
|
||||
living_chat_script_guard_reason: livingChatScriptGuardReason,
|
||||
living_chat_grounding_guard_applied: livingChatGroundingGuardApplied,
|
||||
|
|
|
|||
|
|
@ -117,6 +117,22 @@ function requestsBusinessOverviewNextChecks(
|
|||
);
|
||||
}
|
||||
|
||||
function requestsShortDebtBoundaryFollowup(
|
||||
turnMeaning: Record<string, unknown> | null,
|
||||
graph: Record<string, unknown> | null
|
||||
): boolean {
|
||||
const text = normalizeQuestionText([
|
||||
turnMeaning?.raw_message,
|
||||
turnMeaning?.effective_message,
|
||||
graph?.source_message,
|
||||
graph?.question
|
||||
].join(" "));
|
||||
if (!/\u043f\u0440\u043e\u0441\u0440\u043e\u0447/iu.test(text)) {
|
||||
return false;
|
||||
}
|
||||
return /(?:\u043a\u043e\u0440\u043e\u0442\u043a|\u043f\u043e\u0447\u0435\u043c\u0443|\u0434\u043e\u043a\u0430\u0437\p{L}*)/iu.test(text);
|
||||
}
|
||||
|
||||
function requestsCompactCashflowAnswer(
|
||||
turnMeaning: Record<string, unknown> | null,
|
||||
graph: Record<string, unknown> | null
|
||||
|
|
@ -1086,6 +1102,7 @@ function buildCompactBusinessOverviewReply(
|
|||
actionFamily === "vendor_risk_procurement_boundary" || unsupportedFamily === "vendor_risk_procurement_boundary";
|
||||
const inventoryReserveBoundary =
|
||||
actionFamily === "inventory_reserve_boundary" || unsupportedFamily === "inventory_reserve_liquidation_boundary";
|
||||
const shortDebtBoundaryFollowup = debtDueDateBoundary && requestsShortDebtBoundaryFollowup(turnMeaning, graph);
|
||||
const compactCashflowRequested = directMoneyOnlyAnswer && requestsCompactCashflowAnswer(turnMeaning, graph);
|
||||
const cashflowPolarityRequested = compactCashflowRequested && requestsCashflowPolarityAnswer(turnMeaning, graph);
|
||||
const directAccountingProfitRequested = requestsDirectAccountingProfitAnswer(turnMeaning, graph);
|
||||
|
|
@ -1259,10 +1276,6 @@ function buildCompactBusinessOverviewReply(
|
|||
typeof dueDateAging.rows_with_payment_terms === "number" && Number.isFinite(dueDateAging.rows_with_payment_terms)
|
||||
? dueDateAging.rows_with_payment_terms
|
||||
: null;
|
||||
const rowsWithAmount =
|
||||
typeof dueDateAging.rows_with_amount === "number" && Number.isFinite(dueDateAging.rows_with_amount)
|
||||
? dueDateAging.rows_with_amount
|
||||
: null;
|
||||
const dueDateScopePrefix = organizationScope ? `по компании ${organizationScope} ` : "";
|
||||
if (status === "confirmed_overdue") {
|
||||
lines.push(
|
||||
|
|
@ -1270,13 +1283,23 @@ function buildCompactBusinessOverviewReply(
|
|||
);
|
||||
lines.push("Основа ответа: открытые расчеты 60/62/76, договорный срок оплаты и дата расчетного документа; это проверка просрочки по срокам оплаты, а не просто возраст договора.");
|
||||
} else if (status === "no_payment_terms_configured") {
|
||||
if (shortDebtBoundaryFollowup) {
|
||||
lines.push(
|
||||
`Коротко: да, доказать просрочку ${dueDateScopePrefix}на ${asOfDate} нельзя, потому что в подтвержденных данных не установлен срок оплаты.`
|
||||
);
|
||||
lines.push(
|
||||
`Открытый долг${grossAmount ? ` на ${grossAmount}` : ""} виден, но без due date это задолженность без доказанной договорной просрочки.`
|
||||
);
|
||||
return joinBusinessReplyLines(lines);
|
||||
}
|
||||
lines.push(
|
||||
`Коротко: ${dueDateScopePrefix}на ${asOfDate} подтвержденной просрочки нет: открытые расчеты проверены${grossAmount ? ` на ${grossAmount}` : ""}, но в найденных договорах срок оплаты не установлен.`
|
||||
);
|
||||
lines.push(
|
||||
rowsWithAmount !== null
|
||||
? `Проверено строк с суммой: ${rowsWithAmount}. Без установленного срока оплаты нельзя честно назвать эти остатки просрочкой.`
|
||||
: "Без установленного срока оплаты нельзя честно назвать эти остатки просрочкой."
|
||||
"Без установленного срока оплаты нельзя честно назвать эти остатки просрочкой."
|
||||
);
|
||||
lines.push(
|
||||
"Что проверить дальше: сроки оплаты и даты документов-оснований в договорах/расчетах; без них просрочку доказать нельзя."
|
||||
);
|
||||
} else if (status === "insufficient_due_date_basis") {
|
||||
lines.push(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,11 @@ import {
|
|||
type AssistantMcpDiscoveryResponseCandidateStatus
|
||||
} from "./assistantMcpDiscoveryResponseCandidate";
|
||||
import type { AssistantMcpDiscoveryRuntimeEntryPointContract } from "./assistantMcpDiscoveryRuntimeEntryPoint";
|
||||
import {
|
||||
ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION,
|
||||
type AssistantCoverageGateState,
|
||||
type AssistantTruthAnswerPolicyRuntimeContract
|
||||
} from "../types/assistantRuntimeContracts";
|
||||
|
||||
export const ASSISTANT_MCP_DISCOVERY_RESPONSE_POLICY_SCHEMA_VERSION =
|
||||
"assistant_mcp_discovery_response_policy_v1" as const;
|
||||
|
|
@ -31,6 +36,112 @@ export interface AssistantMcpDiscoveryResponsePolicyResult {
|
|||
reason_codes: string[];
|
||||
}
|
||||
|
||||
function uniqueReasonCodes(values: unknown[]): string[] {
|
||||
const result: string[] = [];
|
||||
values.forEach((value) => {
|
||||
const text = toNonEmptyString(value);
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
const normalized = normalizeReasonCode(text);
|
||||
if (normalized && !result.includes(normalized)) {
|
||||
result.push(normalized);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function patchCoverageGateState(value: unknown, gate: AssistantCoverageGateState): unknown {
|
||||
const source = toRecordObject(value);
|
||||
if (!source) {
|
||||
return value;
|
||||
}
|
||||
const currentGate = toRecordObject(source.coverage_gate_state);
|
||||
return {
|
||||
...source,
|
||||
coverage_gate_state: {
|
||||
...(currentGate ?? {}),
|
||||
coverage_status: gate.coverage_status,
|
||||
evidence_grade: gate.evidence_grade,
|
||||
grounding_status: gate.grounding_status,
|
||||
truth_mode: gate.truth_mode,
|
||||
carryover_eligibility: gate.carryover_eligibility,
|
||||
reason_codes: gate.reason_codes
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function buildAppliedMcpDiscoveryTruthPatch(
|
||||
policy: AssistantMcpDiscoveryResponsePolicyResult,
|
||||
currentDebug?: Record<string, unknown> | null
|
||||
): Record<string, unknown> {
|
||||
if (!policy.applied || !ALLOWED_CANDIDATE_STATUSES.has(policy.candidate.candidate_status)) {
|
||||
return {};
|
||||
}
|
||||
const reasonCodes = uniqueReasonCodes([
|
||||
...policy.reason_codes,
|
||||
"mcp_discovery_response_policy_candidate_applied",
|
||||
"mcp_discovery_response_candidate_guarded_truth_patch"
|
||||
]);
|
||||
const truthGate: AssistantTruthAnswerPolicyRuntimeContract["truth_gate"] = {
|
||||
schema_version: ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION,
|
||||
policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter",
|
||||
coverage_status: "partial",
|
||||
evidence_grade: "medium",
|
||||
grounding_status: "partial",
|
||||
truth_mode: "limited",
|
||||
carryover_eligibility: "root_only",
|
||||
reason_codes: reasonCodes,
|
||||
source_truth_gate_status: "partial_supported",
|
||||
blocked_or_limited_explanation: "mcp_discovery_guarded_bounded_answer"
|
||||
};
|
||||
const answerShape: AssistantTruthAnswerPolicyRuntimeContract["answer_shape"] = {
|
||||
schema_version: ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION,
|
||||
policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter",
|
||||
answer_shape: "limited_with_reason",
|
||||
reply_type: "partial_coverage",
|
||||
capability_contract_id: null,
|
||||
transition_contract_id: null,
|
||||
may_state_confirmed_facts: true,
|
||||
must_include_limitation: true,
|
||||
may_power_followup: true,
|
||||
required_sections: ["direct_answer", "evidence_window", "limitations"],
|
||||
downgrade_only: true
|
||||
};
|
||||
const truthPolicy: AssistantTruthAnswerPolicyRuntimeContract = {
|
||||
schema_version: ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION,
|
||||
policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter",
|
||||
truth_gate: truthGate,
|
||||
answer_shape: answerShape
|
||||
};
|
||||
const debug = currentDebug ?? {};
|
||||
const patch: Record<string, unknown> = {
|
||||
assistant_truth_answer_policy_v1: truthPolicy,
|
||||
coverage_gate_contract: truthGate,
|
||||
answer_shape_contract: answerShape,
|
||||
truth_gate_contract_status: "partial_supported",
|
||||
truth_mode: "limited",
|
||||
carryover_eligibility: "root_only",
|
||||
answer_shape: "limited_with_reason"
|
||||
};
|
||||
const stateTransition = patchCoverageGateState(debug.assistant_state_transition_v1, truthGate);
|
||||
if (stateTransition !== debug.assistant_state_transition_v1) {
|
||||
patch.assistant_state_transition_v1 = stateTransition;
|
||||
}
|
||||
const stateTransitionContract = patchCoverageGateState(debug.state_transition_contract, truthGate);
|
||||
if (stateTransitionContract !== debug.state_transition_contract) {
|
||||
patch.state_transition_contract = stateTransitionContract;
|
||||
}
|
||||
const runtimeContract = toRecordObject(debug.assistant_runtime_contract_v1);
|
||||
if (runtimeContract) {
|
||||
patch.assistant_runtime_contract_v1 = {
|
||||
...runtimeContract,
|
||||
truth_gate_contract_status: "partial_supported"
|
||||
};
|
||||
}
|
||||
return patch;
|
||||
}
|
||||
|
||||
const ALLOWED_CANDIDATE_STATUSES = new Set<AssistantMcpDiscoveryResponseCandidateStatus>([
|
||||
"ready_for_guarded_use",
|
||||
"checked_sources_only_candidate",
|
||||
|
|
|
|||
|
|
@ -235,6 +235,24 @@ describe("assistant deep turn response runtime adapter", () => {
|
|||
|
||||
expect(runtime.response.assistant_reply).toContain("Discovery answer");
|
||||
expect(runtime.debug?.mcp_discovery_response_applied).toBe(true);
|
||||
expect(runtime.debug?.truth_gate_contract_status).toBe("partial_supported");
|
||||
expect(runtime.debug?.truth_mode).toBe("limited");
|
||||
expect(runtime.debug?.coverage_gate_contract).toEqual(
|
||||
expect.objectContaining({
|
||||
coverage_status: "partial",
|
||||
evidence_grade: "medium",
|
||||
grounding_status: "partial",
|
||||
truth_mode: "limited",
|
||||
source_truth_gate_status: "partial_supported"
|
||||
})
|
||||
);
|
||||
expect(runtime.debug?.answer_shape_contract).toEqual(
|
||||
expect.objectContaining({
|
||||
answer_shape: "limited_with_reason",
|
||||
reply_type: "partial_coverage",
|
||||
may_state_confirmed_facts: true
|
||||
})
|
||||
);
|
||||
expect(runFinalizeDeepTurn).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
assistantReply: expect.stringContaining("Discovery answer"),
|
||||
|
|
|
|||
|
|
@ -523,6 +523,116 @@ describe("assistant MCP discovery response candidate", () => {
|
|||
expect(candidate.reply_text).not.toContain("руб..");
|
||||
});
|
||||
|
||||
it("keeps debt due-date boundary answers useful and hides row counters", () => {
|
||||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||||
entryPoint({
|
||||
turn_input: {
|
||||
adapter_status: "ready",
|
||||
turn_meaning_ref: {
|
||||
raw_message: "По ООО Альтернатива Плюс на конец 2020 можно точно понять, какая дебиторка просрочена?",
|
||||
effective_message: "Проверить просроченную дебиторку ООО Альтернатива Плюс на конец 2020.",
|
||||
asked_domain_family: "business_overview",
|
||||
asked_action_family: "debt_due_date_boundary",
|
||||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||||
explicit_date_scope: "2020-12-31"
|
||||
},
|
||||
data_need_graph: {
|
||||
business_fact_family: "business_overview",
|
||||
reason_codes: ["data_need_graph_family_business_overview"]
|
||||
}
|
||||
},
|
||||
bridge: {
|
||||
bridge_status: "answer_draft_ready",
|
||||
user_facing_response_allowed: true,
|
||||
business_fact_answer_allowed: true,
|
||||
requires_user_clarification: false,
|
||||
pilot: {
|
||||
pilot_scope: "business_overview_route_template_v1",
|
||||
derived_business_overview: {
|
||||
debt_due_date_aging: {
|
||||
evidence_status: "no_payment_terms_configured",
|
||||
as_of_date: "2020-12-31",
|
||||
gross_open_amount_human_ru: "35 472 380,36 руб.",
|
||||
rows_with_amount: 46
|
||||
}
|
||||
}
|
||||
},
|
||||
answer_draft: {
|
||||
answer_mode: "confirmed_with_bounded_inference",
|
||||
headline: "Debt boundary.",
|
||||
confirmed_lines: [],
|
||||
inference_lines: [],
|
||||
unknown_lines: [],
|
||||
limitation_lines: [],
|
||||
next_step_line: null
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
expect(candidate.reply_text).toContain("35 472 380,36 руб.");
|
||||
expect(candidate.reply_text).toContain("срок оплаты не установлен");
|
||||
expect(candidate.reply_text).toContain("нельзя честно назвать эти остатки просрочкой");
|
||||
expect(candidate.reply_text).toContain("Что проверить дальше");
|
||||
expect(candidate.reply_text).not.toContain("Проверено строк");
|
||||
expect(candidate.reply_text).not.toContain("строк с суммой");
|
||||
});
|
||||
|
||||
it("answers short debt boundary follow-ups directly instead of replaying the full debt packet", () => {
|
||||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||||
entryPoint({
|
||||
turn_input: {
|
||||
adapter_status: "ready",
|
||||
turn_meaning_ref: {
|
||||
raw_message: "То есть просрочку доказать нельзя, коротко почему?",
|
||||
effective_message: "Коротко объяснить, почему просрочку нельзя доказать.",
|
||||
asked_domain_family: "business_overview",
|
||||
asked_action_family: "debt_due_date_boundary",
|
||||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||||
explicit_date_scope: "2020-12-31"
|
||||
},
|
||||
data_need_graph: {
|
||||
business_fact_family: "business_overview",
|
||||
question: "То есть просрочку доказать нельзя, коротко почему?",
|
||||
reason_codes: ["data_need_graph_family_business_overview"]
|
||||
}
|
||||
},
|
||||
bridge: {
|
||||
bridge_status: "answer_draft_ready",
|
||||
user_facing_response_allowed: true,
|
||||
business_fact_answer_allowed: true,
|
||||
requires_user_clarification: false,
|
||||
pilot: {
|
||||
pilot_scope: "business_overview_route_template_v1",
|
||||
derived_business_overview: {
|
||||
debt_due_date_aging: {
|
||||
evidence_status: "no_payment_terms_configured",
|
||||
as_of_date: "2020-12-31",
|
||||
gross_open_amount_human_ru: "35 472 380,36 руб.",
|
||||
rows_with_amount: 46
|
||||
}
|
||||
}
|
||||
},
|
||||
answer_draft: {
|
||||
answer_mode: "confirmed_with_bounded_inference",
|
||||
headline: "Debt boundary.",
|
||||
confirmed_lines: [],
|
||||
inference_lines: [],
|
||||
unknown_lines: [],
|
||||
limitation_lines: [],
|
||||
next_step_line: null
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
expect(candidate.reply_text?.split("\n")[0]).toContain("доказать просрочку");
|
||||
expect(candidate.reply_text?.split("\n")[0]).toContain("нельзя");
|
||||
expect(candidate.reply_text).toContain("due date");
|
||||
expect(candidate.reply_text).not.toContain("Проверено строк");
|
||||
expect(candidate.reply_text).not.toContain("строк с суммой");
|
||||
});
|
||||
|
||||
it("uses a compact direct answer for business-overview top year questions", () => {
|
||||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||||
entryPoint({
|
||||
|
|
|
|||
Loading…
Reference in New Issue