Этап 4 corrective pack 2 по family isolation после текущих routing fixes

This commit is contained in:
2026-03-29 15:27:01 +03:00
parent 133b6dca3c
commit f74e7b697a
30 changed files with 4427 additions and 259 deletions
@@ -193,7 +193,8 @@ function hasP0ClaimSignal(claimType, focusDomainHint) {
claim === "prove_advance_offset_state" ||
claim === "prove_vat_chain_completeness" ||
claim === "prove_month_close_state" ||
claim === "prove_rbp_tail_state") {
claim === "prove_rbp_tail_state" ||
claim === "prove_fixed_asset_amortization_coverage") {
return true;
}
return (focusDomainHint === "settlements_60_62" ||
@@ -331,6 +332,24 @@ function collectDateSpans(text) {
}
return spans;
}
function collectContractSpans(text) {
const spans = [];
const contractPatterns = [
/(?:\b(?:договор(?:а|у|ом|е)?|contract)\b[^\r\n]{0,24}(?:№|#|n|no\.?)\s*[a-zа-я0-9][a-zа-я0-9/_-]{1,})/giu,
/(?:№|#)\s*[a-zа-я0-9_-]{1,10}\/[a-zа-я0-9_-]{1,12}/giu,
/\b\d{2}\/\d{2}(?:-[a-zа-я]{1,10})?\b/giu
];
for (const contractPattern of contractPatterns) {
let match = null;
while ((match = contractPattern.exec(text)) !== null) {
spans.push({
start: match.index,
end: match.index + match[0].length
});
}
}
return spans;
}
function collectAmountSpans(text) {
const spans = [];
const amountPatterns = [/\b\d{1,3}(?:[ \u00A0]\d{3})+(?:[.,]\d{2})?\b/g, /\b\d+[.,]\d{2}\b/g];
@@ -360,6 +379,11 @@ function collectPercentSpans(text) {
function intersectsAnySpan(start, end, spans) {
return spans.some((span) => start < span.end && end > span.start);
}
function hasAccountContextAround(text, start, end) {
const left = text.slice(Math.max(0, start - 28), start);
const right = text.slice(end, Math.min(text.length, end + 28));
return /(?:счет|сч\.?|account|schet|оплат|расч[её]т|расчет|аванс|зач[её]т|долг|постав|покуп|supplier|customer|settlement|payment|ндс|vat|проводк|posting)/iu.test(`${left} ${right}`);
}
function extractAccountTokens(text) {
const lower = String(text ?? "").toLowerCase();
const explicitAccounts = new Set();
@@ -432,7 +456,8 @@ function extractAccountTokens(text) {
if (explicitAccounts.size > 0) {
return Array.from(explicitAccounts);
}
const spans = [...collectDateSpans(lower), ...collectAmountSpans(lower), ...collectPercentSpans(lower)];
const contractSpans = collectContractSpans(lower);
const spans = [...collectDateSpans(lower), ...collectAmountSpans(lower), ...collectPercentSpans(lower), ...contractSpans];
const hasAccountingLexeme = /(?:\bсчет(?:а|у|ом|ов)?\b|\bсч\.?\b|\baccount(?:s)?\b|\bschet(?:a|u|om|ov)?\b|оплат|расчет|аванс|долг|settlement|payment)/iu.test(lower);
if (!hasAccountingLexeme) {
return [];
@@ -447,6 +472,9 @@ function extractAccountTokens(text) {
if (intersectsAnySpan(start, end, spans)) {
continue;
}
if (!hasAccountContextAround(lower, start, end)) {
continue;
}
const prefix = value.match(/^(\d{2})/)?.[1];
if (!prefix || !knownAccountPrefixes.has(prefix)) {
continue;
@@ -735,6 +763,164 @@ function collectRbpLiveRouteAudit(input) {
plan_override: input.planAudit ?? null
};
}
function enrichFaFragmentForLive(fragmentText, temporalGuard) {
const base = compactWhitespace(String(fragmentText ?? ""));
const hints = [
"Начисление амортизации",
"объект ОС",
"expected set ОС",
"счет 01/02"
];
const effective = temporalGuard && typeof temporalGuard === "object" ? temporalGuard.effective_primary_period : null;
if (effective && effective.from && effective.to) {
hints.push(`период ${effective.from}..${effective.to}`);
}
const hintText = hints.filter(Boolean).join(", ");
if (!base) {
return hintText;
}
if (/амортиз|основн(?:ые|ых)\s+сред|fixed\s*asset|depreciat|счет\s*0[12]|account\s*0[12]/i.test(base)) {
return base;
}
return `${base}; ${hintText}`;
}
function enforceFaLiveRoutePlan(input) {
if (input.claimType !== "prove_fixed_asset_amortization_coverage") {
return {
executionPlan: input.executionPlan,
audit: null
};
}
const requiredLiveCalls = [
"find_amortization_documents_in_period",
"find_fixed_asset_movements_accounts_01_02",
"find_fixed_asset_cards_expected_for_period",
"match_expected_vs_actual_fa_coverage"
];
let routeAdjusted = 0;
let rescuedNoRoute = 0;
const replacedRoutes = [];
const adjustedPlan = input.executionPlan.map((item) => {
if (!item || typeof item !== "object") {
return item;
}
if (item.should_execute !== true && item.no_route_reason === "insufficient_specificity") {
rescuedNoRoute += 1;
routeAdjusted += 1;
return {
...item,
route: "live_mcp_drilldown",
should_execute: true,
no_route_reason: null,
clarification_reason: null,
fragment_text: enrichFaFragmentForLive(item.fragment_text, input.temporalGuard)
};
}
if (item.should_execute === true && item.route !== "hybrid_store_plus_live" && item.route !== "live_mcp_drilldown") {
routeAdjusted += 1;
if (item.route && item.route !== "no_route") {
replacedRoutes.push(String(item.route));
}
return {
...item,
route: "hybrid_store_plus_live",
fragment_text: enrichFaFragmentForLive(item.fragment_text, input.temporalGuard)
};
}
if (item.should_execute === true) {
return {
...item,
fragment_text: enrichFaFragmentForLive(item.fragment_text, input.temporalGuard)
};
}
return item;
});
return {
executionPlan: adjustedPlan,
audit: {
claim_type: "prove_fixed_asset_amortization_coverage",
required_live_calls: requiredLiveCalls,
route_adjustments_applied: routeAdjusted,
rescued_no_route_fragments: rescuedNoRoute,
replaced_routes: Array.from(new Set(replacedRoutes)),
route_gap_reason: routeAdjusted > 0 ? "fa_claim_bound_live_route_override_applied" : null
}
};
}
function collectFaLiveRouteAudit(input) {
if (input.claimType !== "prove_fixed_asset_amortization_coverage") {
return null;
}
const required = new Set(Array.isArray(input.planAudit?.required_live_calls) ? input.planAudit.required_live_calls : []);
const executed = [];
const missing = new Set();
const routeGaps = [];
let matchedRowsTotal = 0;
let returnedRowsTotal = 0;
let fetchedRowsTotal = 0;
for (const result of input.retrievalResults) {
if (!result || typeof result !== "object") {
continue;
}
const summary = result.summary && typeof result.summary === "object" ? result.summary : null;
const live = summary && typeof summary.live_mcp === "object" && summary.live_mcp ? summary.live_mcp : null;
if (!live) {
continue;
}
const requiredCalls = Array.isArray(live.required_live_calls) ? live.required_live_calls : [];
for (const callId of requiredCalls) {
required.add(String(callId ?? "").trim());
}
const executedCalls = Array.isArray(live.executed_live_calls) ? live.executed_live_calls : [];
for (const call of executedCalls) {
if (!call || typeof call !== "object") {
continue;
}
executed.push(call);
}
const missingCalls = Array.isArray(live.missing_live_calls) ? live.missing_live_calls : [];
for (const callId of missingCalls) {
const token = String(callId ?? "").trim();
if (token) {
missing.add(token);
}
}
const routeGapReason = String(live.route_gap_reason ?? "").trim();
if (routeGapReason) {
routeGaps.push(routeGapReason);
}
fetchedRowsTotal += Number(live.fetched_rows ?? 0) || 0;
matchedRowsTotal += Number(live.matched_rows ?? 0) || 0;
returnedRowsTotal += Number(live.returned_rows ?? 0) || 0;
}
const requiredList = Array.from(required).filter(Boolean);
const executedList = executed;
const missingFromExecuted = requiredList.filter((callId) => !executedList.some((item) => String(item.call_id ?? "") === callId));
for (const callId of missingFromExecuted) {
missing.add(callId);
}
const missingList = Array.from(missing);
const routeGapReason = missingList.length > 0
? "required_live_calls_not_executed"
: matchedRowsTotal <= 0
? "claim_live_calls_executed_but_zero_matches"
: routeGaps[0] ?? null;
const executionRate = requiredList.length > 0
? Number(((requiredList.length - missingList.length) / requiredList.length).toFixed(4))
: 1;
return {
claim_type: "prove_fixed_asset_amortization_coverage",
required_live_calls: requiredList,
executed_live_calls: executedList,
missing_live_calls: missingList,
route_gap_reason: routeGapReason,
live_route_execution_rate: executionRate,
fetched_rows_total: fetchedRowsTotal,
matched_rows_total: matchedRowsTotal,
returned_rows_total: returnedRowsTotal,
plan_override: input.planAudit ?? null
};
}
function toDebugRoutes(routeSummary) {
if (!routeSummary) {
return [];
@@ -1201,7 +1387,7 @@ function extractNormalizedPeriodLiteral(text) {
}
function extractFollowupAccountAnchorsLoose(text) {
const lower = String(text ?? "").toLowerCase();
const spans = [...collectDateSpans(lower), ...collectAmountSpans(lower), ...collectPercentSpans(lower)];
const spans = [...collectDateSpans(lower), ...collectAmountSpans(lower), ...collectPercentSpans(lower), ...collectContractSpans(lower)];
const anchors = [];
const followupAccountPattern = /\b(?:01|02|08|19|20|21|23|25|26|28|29|44|51|60|62|68|76|97)(?:\.\d{2})?\b/g;
let match = null;
@@ -1254,27 +1440,8 @@ function hasCrossScopeConflictWithState(userMessage, state) {
return false;
}
function inferP0DomainFromMessage(text) {
const lower = String(text ?? "").toLowerCase();
const accountTokens = extractAccountTokens(lower);
const hasVatAccount = accountTokens.some((token) => /^(?:19|68)(?:\.|$)/.test(token));
const hasSettlementAccount = accountTokens.some((token) => /^(?:51|60|62|76)(?:\.|$)/.test(token));
const hasMonthCloseAccount = accountTokens.some((token) => /^(?:97|2\d|3\d|4[0-4])(?:\.|$)/.test(token));
const hasFixedAssetAccount = accountTokens.some((token) => /^(?:01|02|08)(?:\.|$)/.test(token));
const vatLexical = /(?:ндс|vat|сч[её]т[\s-]?фактур|книг[аи]\s+(?:покуп|продаж)|налогов)/i.test(lower);
const settlementLexical = /(?:долг|аванс|зач[её]т|взаимозач|расч[её]т|оплат|платеж|платёж|постав|покупател)/i.test(lower);
const monthCloseLexical = /(?:закрыти[ея]\s+месяц|закрытие\s+счетов|регламентн|косвенн|затрат|распределени|рбп|финансовых\s+результат)/i.test(lower);
const fixedAssetLexical = /(?:основн(?:ые|ых)?\s+сред|(?:^|[^a-zа-яё])ос(?:$|[^a-zа-яё])|амортиз|depreciat|fixed\s*asset)/i.test(lower);
if (hasVatAccount || vatLexical) {
return "vat_document_register_book";
}
if (fixedAssetLexical || hasFixedAssetAccount) {
return "fixed_asset_amortization";
}
if (monthCloseLexical || hasMonthCloseAccount) {
return "month_close_costs_20_44";
}
if (hasSettlementAccount || settlementLexical) {
return "settlements_60_62";
if (typeof investigationState_1.inferP0DomainFromMessage === "function") {
return investigationState_1.inferP0DomainFromMessage(text);
}
return null;
}
@@ -1554,13 +1721,12 @@ export class AssistantService {
routeSummary: normalized.route_hint_summary
});
const inferredDomainByMessage = inferP0DomainFromMessage(userMessage);
const focusDomainForGuards = inferredDomainByMessage === "fixed_asset_amortization"
? "month_close_costs_20_44"
: inferredDomainByMessage === "settlements_60_62" ||
inferredDomainByMessage === "vat_document_register_book" ||
inferredDomainByMessage === "month_close_costs_20_44"
? inferredDomainByMessage
: null;
const focusDomainForGuards = inferredDomainByMessage === "settlements_60_62" ||
inferredDomainByMessage === "vat_document_register_book" ||
inferredDomainByMessage === "month_close_costs_20_44" ||
inferredDomainByMessage === "fixed_asset_amortization"
? inferredDomainByMessage
: null;
const temporalGuard = (0, assistantRuntimeGuards_1.resolveTemporalGuard)({
userMessage,
normalized: normalized.normalized,
@@ -1592,6 +1758,12 @@ export class AssistantService {
temporalGuard
});
executionPlan = rbpRoutePlanEnforcement.executionPlan;
const faRoutePlanEnforcement = enforceFaLiveRoutePlan({
executionPlan,
claimType: claimAnchorAudit.claim_type,
temporalGuard
});
executionPlan = faRoutePlanEnforcement.executionPlan;
executionPlan = (0, assistantRuntimeGuards_1.applyTemporalHintToExecutionPlan)(executionPlan, temporalGuard);
executionPlan = (0, assistantRuntimeGuards_1.applyPolarityHintToExecutionPlan)(executionPlan, domainPolarityGuardInitial);
const retrievalCalls = [];
@@ -1679,6 +1851,11 @@ export class AssistantService {
retrievalResults,
planAudit: rbpRoutePlanEnforcement.audit
});
const faLiveRouteAudit = collectFaLiveRouteAudit({
claimType: claimAnchorAudit.claim_type,
retrievalResults,
planAudit: faRoutePlanEnforcement.audit
});
const coverageEvaluation = evaluateCoverage(requirementExtraction.requirements, retrievalResults);
const groundingCheckBase = checkGrounding(userMessage, coverageEvaluation.requirements, coverageEvaluation.coverage, retrievalResults);
const groundedAnswerEligibilityGuard = (0, assistantRuntimeGuards_1.evaluateGroundedAnswerEligibility)({
@@ -1792,6 +1969,7 @@ export class AssistantService {
targeted_evidence_acquisition: targetedEvidenceResult.audit,
evidence_admissibility_gate: evidenceGateResult.audit,
...(rbpLiveRouteAudit ? { rbp_live_route_audit: rbpLiveRouteAudit } : {}),
...(faLiveRouteAudit ? { fa_live_route_audit: faLiveRouteAudit } : {}),
eligibility_time_basis: groundedAnswerEligibilityGuard.eligibility_time_basis,
grounded_answer_eligibility_guard: groundedAnswerEligibilityGuard,
...(followupBinding.usage ? { followup_state_usage: followupBinding.usage } : {}),
@@ -1884,6 +2062,8 @@ export class AssistantService {
claim_anchor_audit: claimAnchorAudit,
targeted_evidence_acquisition: targetedEvidenceResult.audit,
evidence_admissibility_gate: evidenceGateResult.audit,
...(rbpLiveRouteAudit ? { rbp_live_route_audit: rbpLiveRouteAudit } : {}),
...(faLiveRouteAudit ? { fa_live_route_audit: faLiveRouteAudit } : {}),
eligibility_time_basis: groundedAnswerEligibilityGuard.eligibility_time_basis,
grounded_answer_eligibility_guard: groundedAnswerEligibilityGuard,
...(followupBinding.usage ? { followup_state_usage: followupBinding.usage } : {}),