Этап 4 / Волна 19.1 выравнивание живого контура для слоя адресного сбора доказательной базы
This commit is contained in:
@@ -82,6 +82,83 @@ function extractExecutionState(normalized) {
|
||||
};
|
||||
});
|
||||
}
|
||||
function collectBusinessScopesFromNormalized(normalized) {
|
||||
const scopes = [];
|
||||
for (const item of extractFragments(normalized)) {
|
||||
if (!item || typeof item !== "object") {
|
||||
continue;
|
||||
}
|
||||
const scope = String(item.business_scope ?? "").trim();
|
||||
if (scope) {
|
||||
scopes.push(scope);
|
||||
}
|
||||
}
|
||||
return Array.from(new Set(scopes));
|
||||
}
|
||||
function hasJuly2020SnapshotSignal(userMessage, companyAnchors) {
|
||||
const lower = String(userMessage ?? "").toLowerCase();
|
||||
if (/(?:\b2020[-/.]0?7\b|\bиюл[ьяе]?\b(?:\s+20\d{2})?|\bjuly\b(?:\s+20\d{2})?)/i.test(lower)) {
|
||||
return true;
|
||||
}
|
||||
const periods = Array.isArray(companyAnchors?.periods) ? companyAnchors.periods : [];
|
||||
const dates = Array.isArray(companyAnchors?.dates) ? companyAnchors.dates : [];
|
||||
return [...periods, ...dates].some((item) => /2020[-/.]0?7|июл|july/i.test(String(item ?? "").toLowerCase()));
|
||||
}
|
||||
function hasP0DomainSignal(userMessage, companyAnchors) {
|
||||
if (inferP0DomainFromMessage(userMessage)) {
|
||||
return true;
|
||||
}
|
||||
const accounts = Array.isArray(companyAnchors?.accounts) ? companyAnchors.accounts : [];
|
||||
if (accounts.some((item) => /^(?:01|02|08|19|20|21|23|25|26|28|29|44|51|60|62|68|76|97)(?:\.|$)/.test(String(item ?? "").trim()))) {
|
||||
return true;
|
||||
}
|
||||
return /(?:ндс|vat|рбп|deferred|амортиз|supplier|customer|settlement|month\s*close|закрыти[ея]\s+месяц|поставщ|покупат)/i.test(String(userMessage ?? "").toLowerCase());
|
||||
}
|
||||
function resolveBusinessScopeAlignment(input) {
|
||||
const rawScopes = collectBusinessScopesFromNormalized(input.normalized);
|
||||
const needsCompanyGrounding = hasJuly2020SnapshotSignal(input.userMessage, input.companyAnchors) && hasP0DomainSignal(input.userMessage, input.companyAnchors);
|
||||
const reasons = [];
|
||||
if (needsCompanyGrounding) {
|
||||
reasons.push("july_2020_snapshot_p0_signal");
|
||||
}
|
||||
if (!input.routeSummary || input.routeSummary.mode !== "deterministic_v2" || !needsCompanyGrounding) {
|
||||
return {
|
||||
business_scope_raw: rawScopes,
|
||||
business_scope_resolved: rawScopes,
|
||||
company_grounding_applied: false,
|
||||
scope_resolution_reason: reasons,
|
||||
route_summary_resolved: input.routeSummary
|
||||
};
|
||||
}
|
||||
let changed = false;
|
||||
const decisions = input.routeSummary.decisions.map((decision) => {
|
||||
const scopeValue = String(decision.business_scope ?? "").trim();
|
||||
if (scopeValue !== "generic_accounting" && scopeValue !== "unclear") {
|
||||
return decision;
|
||||
}
|
||||
changed = true;
|
||||
return {
|
||||
...decision,
|
||||
business_scope: "company_specific_accounting"
|
||||
};
|
||||
});
|
||||
const resolvedSummary = changed
|
||||
? {
|
||||
...input.routeSummary,
|
||||
decisions
|
||||
}
|
||||
: input.routeSummary;
|
||||
const resolvedScopes = changed
|
||||
? Array.from(new Set(decisions.map((decision) => String(decision.business_scope ?? "").trim()).filter(Boolean)))
|
||||
: rawScopes;
|
||||
return {
|
||||
business_scope_raw: rawScopes,
|
||||
business_scope_resolved: resolvedScopes,
|
||||
company_grounding_applied: changed,
|
||||
scope_resolution_reason: changed ? [...reasons, "generic_or_unclear_to_company_specific_override"] : reasons,
|
||||
route_summary_resolved: resolvedSummary
|
||||
};
|
||||
}
|
||||
function escapeRegex(value) {
|
||||
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
@@ -141,8 +218,9 @@ function extractDiscardedIntentSegments(normalized) {
|
||||
function collectDateSpans(text) {
|
||||
const spans = [];
|
||||
const datePatterns = [
|
||||
/\b20\d{2}[-/.](?:0[1-9]|1[0-2])(?:[-/.](?:0[1-9]|[12]\d|3[01]))?\b/g,
|
||||
/\b(?:0?[1-9]|[12]\d|3[01])[./-](?:0?[1-9]|1[0-2])[./-](?:\d{2}|\d{4})\b/g
|
||||
/\b20\d{2}(?:[-/.](?:0?[1-9]|1[0-2]))(?:[-/.](?:0?[1-9]|[12]\d|3[01]))?\b/g,
|
||||
/\b(?:0?[1-9]|[12]\d|3[01])[./-](?:0?[1-9]|1[0-2])[./-](?:\d{2}|\d{4})\b/g,
|
||||
/\b(?:0?[1-9]|[12]\d|3[01])\s+(?:январ[ьяе]|феврал[ьяе]|март[ае]?|апрел[ьяе]|ма[йея]|июн[ьяе]?|июл[ьяе]?|август[ае]?|сентябр[ьяе]?|октябр[ьяе]?|ноябр[ьяе]?|декабр[ьяе]?|january|february|march|april|may|june|july|august|september|october|november|december)(?:\s+20\d{2})?\b/giu
|
||||
];
|
||||
for (const datePattern of datePatterns) {
|
||||
let match = null;
|
||||
@@ -155,6 +233,32 @@ function collectDateSpans(text) {
|
||||
}
|
||||
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];
|
||||
for (const amountPattern of amountPatterns) {
|
||||
let match = null;
|
||||
while ((match = amountPattern.exec(text)) !== null) {
|
||||
spans.push({
|
||||
start: match.index,
|
||||
end: match.index + match[0].length
|
||||
});
|
||||
}
|
||||
}
|
||||
return spans;
|
||||
}
|
||||
function collectPercentSpans(text) {
|
||||
const spans = [];
|
||||
const percentPattern = /\b\d{1,3}(?:[.,]\d+)?\s*%/g;
|
||||
let match = null;
|
||||
while ((match = percentPattern.exec(text)) !== null) {
|
||||
spans.push({
|
||||
start: match.index,
|
||||
end: match.index + match[0].length
|
||||
});
|
||||
}
|
||||
return spans;
|
||||
}
|
||||
function intersectsAnySpan(start, end, spans) {
|
||||
return spans.some((span) => start < span.end && end > span.start);
|
||||
}
|
||||
@@ -230,7 +334,7 @@ function extractAccountTokens(text) {
|
||||
if (explicitAccounts.size > 0) {
|
||||
return Array.from(explicitAccounts);
|
||||
}
|
||||
const spans = collectDateSpans(lower);
|
||||
const spans = [...collectDateSpans(lower), ...collectAmountSpans(lower), ...collectPercentSpans(lower)];
|
||||
const hasAccountingLexeme = /(?:\bсчет(?:а|у|ом|ов)?\b|\bсч\.?\b|\baccount(?:s)?\b|\bschet(?:a|u|om|ov)?\b|оплат|расчет|аванс|долг|settlement|payment)/iu.test(lower);
|
||||
if (!hasAccountingLexeme) {
|
||||
return [];
|
||||
@@ -846,7 +950,7 @@ function extractNormalizedPeriodLiteral(text) {
|
||||
}
|
||||
function extractFollowupAccountAnchorsLoose(text) {
|
||||
const lower = String(text ?? "").toLowerCase();
|
||||
const spans = collectDateSpans(lower);
|
||||
const spans = [...collectDateSpans(lower), ...collectAmountSpans(lower), ...collectPercentSpans(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;
|
||||
@@ -1192,6 +1296,13 @@ export class AssistantService {
|
||||
};
|
||||
const normalized = await this.normalizerService.normalize(normalizePayload);
|
||||
const companyAnchors = (0, companyAnchorResolver_1.resolveCompanyAnchors)(userMessage);
|
||||
const businessScopeResolution = resolveBusinessScopeAlignment({
|
||||
userMessage,
|
||||
companyAnchors,
|
||||
normalized: normalized.normalized,
|
||||
routeSummary: normalized.route_hint_summary
|
||||
});
|
||||
const resolvedRouteSummary = businessScopeResolution.route_summary_resolved;
|
||||
const inferredDomainByMessage = inferP0DomainFromMessage(userMessage);
|
||||
const focusDomainForGuards = inferredDomainByMessage === "settlements_60_62" ||
|
||||
inferredDomainByMessage === "vat_document_register_book" ||
|
||||
@@ -1214,8 +1325,8 @@ export class AssistantService {
|
||||
focusDomainHint: focusDomainForGuards,
|
||||
primaryPeriod: temporalGuard.primary_period_window
|
||||
});
|
||||
const requirementExtraction = extractRequirements(normalized.route_hint_summary, normalized.normalized, userMessage);
|
||||
let executionPlan = toExecutionPlan(normalized.route_hint_summary, normalized.normalized, userMessage, requirementExtraction.byFragment);
|
||||
const requirementExtraction = extractRequirements(resolvedRouteSummary, normalized.normalized, userMessage);
|
||||
let executionPlan = toExecutionPlan(resolvedRouteSummary, normalized.normalized, userMessage, requirementExtraction.byFragment);
|
||||
executionPlan = (0, assistantRuntimeGuards_1.applyTemporalHintToExecutionPlan)(executionPlan, temporalGuard);
|
||||
executionPlan = (0, assistantRuntimeGuards_1.applyPolarityHintToExecutionPlan)(executionPlan, domainPolarityGuardInitial);
|
||||
const retrievalCalls = [];
|
||||
@@ -1305,7 +1416,8 @@ export class AssistantService {
|
||||
polarity: polarityGuardResult.audit,
|
||||
evidence: evidenceGateResult.audit,
|
||||
claimAnchors: claimAnchorAudit,
|
||||
targetedEvidenceHitRate: targetedEvidenceResult.audit.targeted_evidence_hit_rate
|
||||
targetedEvidenceHitRate: targetedEvidenceResult.audit.targeted_evidence_hit_rate,
|
||||
businessScopeResolved: businessScopeResolution.business_scope_resolved
|
||||
});
|
||||
const groundingCheck = (0, assistantRuntimeGuards_1.applyEligibilityToGroundingCheck)(groundingCheckBase, groundedAnswerEligibilityGuard);
|
||||
const focusDomainHint = followupBinding.usage?.applied
|
||||
@@ -1317,7 +1429,7 @@ export class AssistantService {
|
||||
const normalizationPeriodExplicit = hasExplicitPeriodAnchorFromNormalized(normalized.normalized) || hasPeriodInCompanyAnchors;
|
||||
const composition = (0, answerComposer_1.composeAssistantAnswer)({
|
||||
userMessage,
|
||||
routeSummary: normalized.route_hint_summary,
|
||||
routeSummary: resolvedRouteSummary,
|
||||
retrievalResults,
|
||||
requirements: coverageEvaluation.requirements,
|
||||
coverageReport: coverageEvaluation.coverage,
|
||||
@@ -1351,7 +1463,7 @@ export class AssistantService {
|
||||
timestamp: new Date().toISOString(),
|
||||
questionId: userItem.message_id,
|
||||
userMessage,
|
||||
routeSummary: normalized.route_hint_summary,
|
||||
routeSummary: resolvedRouteSummary,
|
||||
requirements: coverageEvaluation.requirements,
|
||||
coverageReport: coverageEvaluation.coverage,
|
||||
retrievalResults,
|
||||
@@ -1367,11 +1479,11 @@ export class AssistantService {
|
||||
prompt_version: normalized.prompt_version,
|
||||
schema_version: normalized.schema_version,
|
||||
fallback_type: composition.fallback_type,
|
||||
route_summary: normalized.route_hint_summary,
|
||||
route_summary: resolvedRouteSummary,
|
||||
fragments: extractFragments(normalized.normalized),
|
||||
requirements_extracted: coverageEvaluation.requirements,
|
||||
coverage_report: coverageEvaluation.coverage,
|
||||
routes: toDebugRoutes(normalized.route_hint_summary),
|
||||
routes: toDebugRoutes(resolvedRouteSummary),
|
||||
retrieval_status: retrievalResults.map((item) => ({
|
||||
fragment_id: item.fragment_id,
|
||||
requirement_ids: item.requirement_ids,
|
||||
@@ -1384,16 +1496,29 @@ export class AssistantService {
|
||||
dropped_intent_segments: extractDiscardedIntentSegments(normalized.normalized),
|
||||
question_type_class: questionTypeClass,
|
||||
company_anchors: companyAnchors,
|
||||
business_scope_raw: businessScopeResolution.business_scope_raw,
|
||||
business_scope_resolved: businessScopeResolution.business_scope_resolved,
|
||||
company_grounding_applied: businessScopeResolution.company_grounding_applied,
|
||||
scope_resolution_reason: businessScopeResolution.scope_resolution_reason,
|
||||
raw_time_anchor: temporalGuard.raw_time_anchor,
|
||||
raw_time_scope: temporalGuard.raw_time_scope,
|
||||
resolved_time_anchor: temporalGuard.resolved_time_anchor,
|
||||
resolved_primary_period: temporalGuard.resolved_primary_period,
|
||||
temporal_alignment_status: temporalGuard.temporal_alignment_status,
|
||||
temporal_resolution_source: temporalGuard.temporal_resolution_source,
|
||||
temporal_guard_basis: temporalGuard.temporal_guard_basis,
|
||||
temporal_guard_applied: temporalGuard.temporal_guard_applied,
|
||||
temporal_guard_outcome: temporalGuard.temporal_guard_outcome,
|
||||
temporal_guard: temporalGuard,
|
||||
raw_numeric_tokens: polarityGuardResult.audit.raw_numeric_tokens,
|
||||
classified_numeric_tokens: polarityGuardResult.audit.classified_numeric_tokens,
|
||||
rejected_as_non_accounts: polarityGuardResult.audit.rejected_as_non_accounts,
|
||||
resolved_account_anchors: polarityGuardResult.audit.resolved_account_anchors,
|
||||
domain_polarity_guard: polarityGuardResult.audit,
|
||||
claim_anchor_audit: claimAnchorAudit,
|
||||
targeted_evidence_acquisition: targetedEvidenceResult.audit,
|
||||
evidence_admissibility_gate: evidenceGateResult.audit,
|
||||
eligibility_time_basis: groundedAnswerEligibilityGuard.eligibility_time_basis,
|
||||
grounded_answer_eligibility_guard: groundedAnswerEligibilityGuard,
|
||||
...(followupBinding.usage ? { followup_state_usage: followupBinding.usage } : {}),
|
||||
problem_centric_answer_applied: composition.problem_centric_answer_applied ?? false,
|
||||
@@ -1438,7 +1563,7 @@ export class AssistantService {
|
||||
normalizer_output: normalized.normalized,
|
||||
execution_plan: executionPlan,
|
||||
resolved_execution_state: extractExecutionState(normalized.normalized),
|
||||
routes: toDebugRoutes(normalized.route_hint_summary),
|
||||
routes: toDebugRoutes(resolvedRouteSummary),
|
||||
retrieval_calls: retrievalCalls,
|
||||
retrieval_results_raw: retrievalResultsRaw,
|
||||
retrieval_results_normalized: retrievalResults,
|
||||
@@ -1460,16 +1585,29 @@ export class AssistantService {
|
||||
dropped_intent_segments: extractDiscardedIntentSegments(normalized.normalized),
|
||||
question_type_class: questionTypeClass,
|
||||
company_anchors: companyAnchors,
|
||||
business_scope_raw: businessScopeResolution.business_scope_raw,
|
||||
business_scope_resolved: businessScopeResolution.business_scope_resolved,
|
||||
company_grounding_applied: businessScopeResolution.company_grounding_applied,
|
||||
scope_resolution_reason: businessScopeResolution.scope_resolution_reason,
|
||||
raw_time_anchor: temporalGuard.raw_time_anchor,
|
||||
raw_time_scope: temporalGuard.raw_time_scope,
|
||||
resolved_time_anchor: temporalGuard.resolved_time_anchor,
|
||||
resolved_primary_period: temporalGuard.resolved_primary_period,
|
||||
temporal_alignment_status: temporalGuard.temporal_alignment_status,
|
||||
temporal_resolution_source: temporalGuard.temporal_resolution_source,
|
||||
temporal_guard_basis: temporalGuard.temporal_guard_basis,
|
||||
temporal_guard_applied: temporalGuard.temporal_guard_applied,
|
||||
temporal_guard_outcome: temporalGuard.temporal_guard_outcome,
|
||||
temporal_guard: temporalGuard,
|
||||
raw_numeric_tokens: polarityGuardResult.audit.raw_numeric_tokens,
|
||||
classified_numeric_tokens: polarityGuardResult.audit.classified_numeric_tokens,
|
||||
rejected_as_non_accounts: polarityGuardResult.audit.rejected_as_non_accounts,
|
||||
resolved_account_anchors: polarityGuardResult.audit.resolved_account_anchors,
|
||||
domain_polarity_guard: polarityGuardResult.audit,
|
||||
claim_anchor_audit: claimAnchorAudit,
|
||||
targeted_evidence_acquisition: targetedEvidenceResult.audit,
|
||||
evidence_admissibility_gate: evidenceGateResult.audit,
|
||||
eligibility_time_basis: groundedAnswerEligibilityGuard.eligibility_time_basis,
|
||||
grounded_answer_eligibility_guard: groundedAnswerEligibilityGuard,
|
||||
...(followupBinding.usage ? { followup_state_usage: followupBinding.usage } : {}),
|
||||
problem_centric_answer_applied: composition.problem_centric_answer_applied ?? false,
|
||||
|
||||
Reference in New Issue
Block a user