Этап 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
@@ -5,7 +5,12 @@ import type { EvidenceItem } from "../types/stage1Contracts";
import type { ProblemUnit } from "../types/stage2ProblemUnits";
import type { ClaimBoundAnchorAudit } from "./assistantClaimBoundEvidence";
type P0DomainHint = "settlements_60_62" | "vat_document_register_book" | "month_close_costs_20_44" | null;
type P0DomainHint =
| "settlements_60_62"
| "vat_document_register_book"
| "month_close_costs_20_44"
| "fixed_asset_amortization"
| null;
const JULY_YEAR = "2020";
const JULY_MONTH = "07";
@@ -161,10 +166,37 @@ function collectPercentLikeSpans(text: string): Array<{ start: number; end: numb
return spans;
}
function collectContractLikeSpans(text: string): Array<{ start: number; end: number }> {
const spans: Array<{ start: number; end: number }> = [];
const patterns = [
/(?:\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 pattern of patterns) {
let match: RegExpExecArray | null = null;
while ((match = pattern.exec(text)) !== null) {
spans.push({
start: match.index,
end: match.index + match[0].length
});
}
}
return spans;
}
function intersectsSpan(start: number, end: number, spans: Array<{ start: number; end: number }>): boolean {
return spans.some((span) => start < span.end && end > span.start);
}
function hasAccountContextAround(text: string, start: number, end: number): boolean {
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}`
);
}
interface AccountExtractionAudit {
resolved_account_anchors: string[];
raw_numeric_tokens: string[];
@@ -181,7 +213,12 @@ function extractAccountsFromTextDetailed(text: string, options?: { forceAccountC
const dateSpans = collectDateLikeSpans(lower);
const amountSpans = collectAmountLikeSpans(lower);
const percentSpans = collectPercentLikeSpans(lower);
const blockedSpans = [...dateSpans, ...amountSpans, ...percentSpans];
const contractSpans = collectContractLikeSpans(lower);
const blockedSpans = [...dateSpans, ...amountSpans, ...percentSpans, ...contractSpans];
const hasAccountingLexeme =
/(?:\bсчет(?:а|у|ом|ов)?\b|\bсч\.?\b|\baccount(?:s)?\b|\bschet(?:a|u|om|ov)?\b|оплат|расч[её]т|расчет|аванс|долг|settlement|payment|supplier|customer|постав|покуп)/iu.test(
lower
);
const contextualPattern =
/(?:\b(?:счет(?:а|у|ом|ов)?|сч\.?|account(?:s)?|schet(?:a|u|om|ov)?)\b\s*(?:№|#|:)?\s*)(\d{2}(?:\.\d{2})?)/giu;
let contextualMatch: RegExpExecArray | null = null;
@@ -246,6 +283,14 @@ function extractAccountsFromTextDetailed(text: string, options?: { forceAccountC
rejectedAsNonAccounts.add(token);
continue;
}
if (intersectsSpan(start, end, contractSpans)) {
classifiedNumericTokens.push({
token,
classification: "other_numeric"
});
rejectedAsNonAccounts.add(token);
continue;
}
if (!prefix || !KNOWN_ACCOUNT_PREFIXES.has(prefix)) {
classifiedNumericTokens.push({
token,
@@ -254,6 +299,14 @@ function extractAccountsFromTextDetailed(text: string, options?: { forceAccountC
rejectedAsNonAccounts.add(token);
continue;
}
if (!hasAccountingLexeme || !hasAccountContextAround(lower, start, end)) {
classifiedNumericTokens.push({
token,
classification: "other_numeric"
});
rejectedAsNonAccounts.add(token);
continue;
}
accounts.add(token);
classifiedNumericTokens.push({
token,
@@ -509,7 +562,7 @@ function resolveJulyAnchor(rawText: string): TemporalAnchorResolution {
const raw = String(rawText ?? "");
const lower = raw.toLowerCase();
const explicitYear = lower.match(/\b(20\d{2})\b/)?.[1] ?? null;
const dayByNamedJuly = lower.match(/(?:^|\D)(0?[1-9]|[12]\d|3[01])\s+(?:июл(?:я|ь)?|july|РёСЋР»(?:СЏ|СЊ)?)(?:\D|$)/i);
const dayByNamedJuly = lower.match(/(?:^|\D)(0?[1-9]|[12]\d|3[01])\s+(?:июл(?:я|ь)?|july)(?:\D|$)/i);
const dayByNumeric = lower.match(/\b(0?[1-9]|[12]\d|3[01])[./-](0?7)(?:[./-](\d{2}|\d{4}))?\b/);
const monthByNamed = /(?:июл|july|РёСЋР»)/i.test(lower);
const monthByNumeric = /\b20\d{2}[-/.]0?7\b/.test(lower);
@@ -1044,6 +1097,10 @@ function isMonthClosePrefix(prefix: string): boolean {
return numeric >= 20 && numeric <= 44;
}
function isFixedAssetPrefix(prefix: string): boolean {
return prefix === "01" || prefix === "02" || prefix === "08";
}
function expectedAccountPrefixes(input: {
focusDomainHint: P0DomainHint;
polarity: DomainPolarity;
@@ -1062,6 +1119,9 @@ function expectedAccountPrefixes(input: {
if (input.focusDomainHint === "month_close_costs_20_44") {
return ["20", "25", "26", "44", "97", "01", "02", "08"];
}
if (input.focusDomainHint === "fixed_asset_amortization") {
return ["01", "02", "08"];
}
if (input.focusDomainHint === "settlements_60_62") {
if (input.polarity === "supplier_payable") {
return ["60", "51", "76"];
@@ -1121,6 +1181,13 @@ function hasWrongDomainByAccounts(accounts: string[], focusDomainHint: P0DomainH
if (focusDomainHint === "month_close_costs_20_44") {
return prefixes.every((prefix) => isSettlementPrefix(prefix) || isVatPrefix(prefix));
}
if (focusDomainHint === "fixed_asset_amortization") {
const hasFixedAsset = prefixes.some((prefix) => isFixedAssetPrefix(prefix));
if (hasFixedAsset) {
return false;
}
return prefixes.every((prefix) => isSettlementPrefix(prefix) || isVatPrefix(prefix) || isMonthClosePrefix(prefix));
}
return false;
}
@@ -1531,3 +1598,4 @@ export function applyEligibilityToGroundingCheck<T extends { status: string; rea
};
}