Этап 4 добивка двух хвостов: settlement polarity и чистое разделение month_close / RBP в live acceptance

This commit is contained in:
2026-03-29 16:30:10 +03:00
parent f74e7b697a
commit c82ebd70b7
12 changed files with 705 additions and 19 deletions
@@ -26,6 +26,8 @@ export interface TemporalWindow {
export interface ClaimBoundAnchorAudit {
claim_type: ClaimType;
settlement_role?: "supplier" | "customer" | "mixed" | "unknown";
settlement_role_resolution_reason?: string[];
polarity_resolution_status?: "resolved_supplier" | "resolved_customer" | "mixed" | "unknown" | "not_applicable";
required_anchors: string[];
resolved_anchors: Record<string, string[]>;
missing_anchors: string[];
@@ -431,12 +433,29 @@ export function resolveClaimBoundAnchors(input: {
if (!allowedContextWindow && input.primaryPeriod) {
reasonCodes.push("controlled_temporal_expansion_window_unavailable");
}
const settlementRole = resolveSettlementRole({
const settlementRoleRaw = resolveSettlementRole({
claimType,
counterpartyScope: resolvedAnchors.counterparty_scope ?? [],
accountPrefixes,
userMessage: input.userMessage
});
const settlementRole = typeof settlementRoleRaw === "string" ? settlementRoleRaw : undefined;
const settlementRoleReason: string[] =
claimType === "prove_settlement_closure_state" || claimType === "prove_advance_offset_state"
? settlementRole
? [`settlement_role_resolved_${settlementRole}`]
: ["no_supplier_customer_anchor"]
: [];
const polarityResolutionStatus =
claimType === "prove_settlement_closure_state" || claimType === "prove_advance_offset_state"
? settlementRole === "supplier"
? "resolved_supplier"
: settlementRole === "customer"
? "resolved_customer"
: settlementRole === "mixed"
? "mixed"
: "unknown"
: "not_applicable";
if (
(claimType === "prove_settlement_closure_state" || claimType === "prove_advance_offset_state") &&
(settlementRole === "mixed" || settlementRole === "unknown")
@@ -447,6 +466,8 @@ export function resolveClaimBoundAnchors(input: {
return {
claim_type: claimType,
settlement_role: settlementRole,
settlement_role_resolution_reason: settlementRoleReason,
polarity_resolution_status: polarityResolutionStatus,
required_anchors: requiredAnchors,
resolved_anchors: resolvedAnchors,
missing_anchors: missingAnchors,
@@ -848,12 +848,25 @@ export function resolveDomainPolarityGuard(input: {
(/(?:сч[её]т\s*62|по\s*62|счет\s*62|РїРѕ\s*62)/i.test(lower) ? 1 : 0);
let polarity: DomainPolarity = "mixed_or_unresolved";
if (supplierScore > 0 && customerScore === 0) {
polarity = "supplier_payable";
} else if (customerScore > 0 && supplierScore === 0) {
polarity = "customer_receivable";
if (supplierScore > 0 || customerScore > 0) {
if (supplierScore >= customerScore + 2) {
polarity = "supplier_payable";
} else if (customerScore >= supplierScore + 2) {
polarity = "customer_receivable";
} else if (prefixes.has("60") && !prefixes.has("62")) {
polarity = "supplier_payable";
} else if (prefixes.has("62") && !prefixes.has("60")) {
polarity = "customer_receivable";
}
}
const unresolved = polarity === "mixed_or_unresolved";
const reasonCodes = unresolved ? ["unresolved_supplier_customer_polarity"] : [];
if (unresolved && supplierScore > 0 && customerScore > 0) {
reasonCodes.push("supplier_customer_signals_conflict");
}
if (unresolved && supplierScore === 0 && customerScore === 0) {
reasonCodes.push("supplier_customer_signals_absent");
}
return {
applied: true,
polarity,
@@ -868,7 +881,7 @@ export function resolveDomainPolarityGuard(input: {
rejected_problem_units: 0,
rejected_evidence: 0,
critical_contradiction: unresolved,
reason_codes: unresolved ? ["unresolved_supplier_customer_polarity"] : []
reason_codes: uniqueStrings(reasonCodes)
};
}
@@ -202,18 +202,45 @@ function hasP0ClaimSignal(claimType, focusDomainHint) {
focusDomainHint === "month_close_costs_20_44" ||
focusDomainHint === "fixed_asset_amortization");
}
function hasSettlementScopeSignal(input) {
const claim = String(input.claimType ?? "").trim();
const domain = String(input.focusDomainHint ?? "").trim();
if (claim === "prove_settlement_closure_state" || claim === "prove_advance_offset_state" || domain === "settlements_60_62") {
return true;
}
if (Boolean(input.followupApplied) && domain === "settlements_60_62") {
return true;
}
const lower = String(input.userMessage ?? "").toLowerCase();
if (/(?:60(?:\\.\\d{2})?|62(?:\\.\\d{2})?|76(?:\\.\\d{2})?|оплат|расч[её]т|зач[её]т|аванс|долг|хвост|supplier|customer|settlement|payable|receivable|поставщ|покупат)/i.test(lower)) {
return true;
}
const accounts = Array.isArray(input.companyAnchors?.accounts) ? input.companyAnchors.accounts : [];
return accounts.some((item) => /^(?:51|60|62|76)(?:\\.|$)/.test(String(item ?? "").trim()));
}
function resolveBusinessScopeFromLiveContext(input) {
const current = input.current;
const routeSummary = current?.route_summary_resolved;
const julyResolved = isJuly2020TemporalResolved(input.temporalGuard);
const p0Signal = hasP0ClaimSignal(input.claimType, input.focusDomainHint);
if (!julyResolved || !p0Signal) {
const settlementScopeSignal = hasSettlementScopeSignal({
userMessage: input.userMessage,
companyAnchors: input.companyAnchors,
claimType: input.claimType,
focusDomainHint: input.focusDomainHint,
followupApplied: input.followupApplied
});
const shouldRecoverScope = p0Signal && (julyResolved || settlementScopeSignal);
if (!shouldRecoverScope) {
return current;
}
const reasons = Array.isArray(current.scope_resolution_reason) ? [...current.scope_resolution_reason] : [];
if (!reasons.includes("temporal_claim_bound_company_scope_recovery")) {
if (julyResolved && !reasons.includes("temporal_claim_bound_company_scope_recovery")) {
reasons.push("temporal_claim_bound_company_scope_recovery");
}
if (settlementScopeSignal && !reasons.includes("settlement_claim_company_scope_recovery")) {
reasons.push("settlement_claim_company_scope_recovery");
}
const currentScopes = Array.isArray(current.business_scope_resolved) ? current.business_scope_resolved : [];
let changed = false;
const normalizedScopes = currentScopes
@@ -1747,7 +1774,10 @@ export class AssistantService {
current: initialBusinessScopeResolution,
temporalGuard,
claimType: claimAnchorAudit.claim_type,
focusDomainHint: focusDomainForGuards
focusDomainHint: focusDomainForGuards,
userMessage,
companyAnchors,
followupApplied: Boolean(followupBinding.usage?.applied)
});
const resolvedRouteSummary = businessScopeResolution.route_summary_resolved;
const requirementExtraction = extractRequirements(resolvedRouteSummary, normalized.normalized, userMessage);
@@ -1966,6 +1996,9 @@ export class AssistantService {
resolved_account_anchors: polarityGuardResult.audit.resolved_account_anchors,
domain_polarity_guard: polarityGuardResult.audit,
claim_anchor_audit: claimAnchorAudit,
settlement_role: claimAnchorAudit.settlement_role ?? null,
settlement_role_resolution_reason: claimAnchorAudit.settlement_role_resolution_reason ?? [],
polarity_resolution_status: claimAnchorAudit.polarity_resolution_status ?? "not_applicable",
targeted_evidence_acquisition: targetedEvidenceResult.audit,
evidence_admissibility_gate: evidenceGateResult.audit,
...(rbpLiveRouteAudit ? { rbp_live_route_audit: rbpLiveRouteAudit } : {}),
@@ -2060,6 +2093,9 @@ export class AssistantService {
resolved_account_anchors: polarityGuardResult.audit.resolved_account_anchors,
domain_polarity_guard: polarityGuardResult.audit,
claim_anchor_audit: claimAnchorAudit,
settlement_role: claimAnchorAudit.settlement_role ?? null,
settlement_role_resolution_reason: claimAnchorAudit.settlement_role_resolution_reason ?? [],
polarity_resolution_status: claimAnchorAudit.polarity_resolution_status ?? "not_applicable",
targeted_evidence_acquisition: targetedEvidenceResult.audit,
evidence_admissibility_gate: evidenceGateResult.audit,
...(rbpLiveRouteAudit ? { rbp_live_route_audit: rbpLiveRouteAudit } : {}),
@@ -120,6 +120,8 @@ export interface ClaimBoundAnchorAuditDebug {
| "prove_rbp_tail_state"
| "prove_fixed_asset_amortization_coverage";
settlement_role?: "supplier" | "customer" | "mixed" | "unknown";
settlement_role_resolution_reason?: string[];
polarity_resolution_status?: "resolved_supplier" | "resolved_customer" | "mixed" | "unknown" | "not_applicable";
required_anchors: string[];
resolved_anchors: Record<string, string[]>;
missing_anchors: string[];
@@ -341,6 +343,9 @@ export interface AssistantDebugPayload {
resolved_account_anchors?: string[];
domain_polarity_guard?: DomainPolarityGuardDebug;
claim_anchor_audit?: ClaimBoundAnchorAuditDebug;
settlement_role?: ClaimBoundAnchorAuditDebug["settlement_role"];
settlement_role_resolution_reason?: ClaimBoundAnchorAuditDebug["settlement_role_resolution_reason"];
polarity_resolution_status?: ClaimBoundAnchorAuditDebug["polarity_resolution_status"];
targeted_evidence_acquisition?: TargetedEvidenceAcquisitionDebug;
evidence_admissibility_gate?: EvidenceAdmissibilityGateDebug;
rbp_live_route_audit?: RbpLiveRouteAuditDebug;