Этап 4 / Волна 19.1 выравнивание живого контура для слоя адресного сбора доказательной базы
This commit is contained in:
@@ -114,15 +114,81 @@ function accountPrefix(value: string): string | null {
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
function extractAccountsFromText(text: string): string[] {
|
||||
function collectDateLikeSpans(text: string): Array<{ start: number; end: number }> {
|
||||
const spans: Array<{ start: number; end: number }> = [];
|
||||
const patterns = [
|
||||
/\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 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 collectAmountLikeSpans(text: string): Array<{ start: number; end: number }> {
|
||||
const spans: Array<{ start: number; end: number }> = [];
|
||||
const patterns = [/\b\d{1,3}(?:[ \u00A0]\d{3})+(?:[.,]\d{2})?\b/g, /\b\d+[.,]\d{2}\b/g];
|
||||
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 collectPercentLikeSpans(text: string): Array<{ start: number; end: number }> {
|
||||
const spans: Array<{ start: number; end: number }> = [];
|
||||
const pattern = /\b\d{1,3}(?:[.,]\d+)?\s*%/g;
|
||||
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);
|
||||
}
|
||||
|
||||
interface AccountExtractionAudit {
|
||||
resolved_account_anchors: string[];
|
||||
raw_numeric_tokens: string[];
|
||||
classified_numeric_tokens: Array<{
|
||||
token: string;
|
||||
classification: "account_token" | "date_token" | "amount_token" | "percent_token" | "other_numeric";
|
||||
}>;
|
||||
rejected_as_non_accounts: string[];
|
||||
}
|
||||
|
||||
function extractAccountsFromTextDetailed(text: string, options?: { forceAccountContext?: boolean }): AccountExtractionAudit {
|
||||
const lower = String(text ?? "").toLowerCase();
|
||||
const accounts = new Set<string>();
|
||||
const dateSpans = collectDateLikeSpans(lower);
|
||||
const amountSpans = collectAmountLikeSpans(lower);
|
||||
const percentSpans = collectPercentLikeSpans(lower);
|
||||
const blockedSpans = [...dateSpans, ...amountSpans, ...percentSpans];
|
||||
const contextualPattern =
|
||||
/(?:\b(?:СЃС‡(?:Рµ|С‘)С‚(?:Р°|Сѓ|РѕРј|РѕРІ)?|account|schet)\b\s*(?:в„–|#|:)?\s*)(\d{2}(?:\.\d{2})?)/giu;
|
||||
/(?:\b(?:счет(?:а|у|ом|ов)?|сч\.?|account(?:s)?|schet(?:a|u|om|ov)?)\b\s*(?:№|#|:)?\s*)(\d{2}(?:\.\d{2})?)/giu;
|
||||
let contextualMatch: RegExpExecArray | null = null;
|
||||
while ((contextualMatch = contextualPattern.exec(lower)) !== null) {
|
||||
const token = String(contextualMatch[1] ?? "").trim();
|
||||
if (token) {
|
||||
const prefix = token.match(/^(\d{2})/)?.[1] ?? null;
|
||||
if (token && prefix && KNOWN_ACCOUNT_PREFIXES.has(prefix)) {
|
||||
accounts.add(token);
|
||||
}
|
||||
}
|
||||
@@ -131,35 +197,139 @@ function extractAccountsFromText(text: string): string[] {
|
||||
while ((pairMatch = pairPattern.exec(lower)) !== null) {
|
||||
const left = String(pairMatch[1] ?? "").trim();
|
||||
const right = String(pairMatch[2] ?? "").trim();
|
||||
if (left) accounts.add(left);
|
||||
if (right) accounts.add(right);
|
||||
const leftPrefix = left.match(/^(\d{2})/)?.[1] ?? null;
|
||||
const rightPrefix = right.match(/^(\d{2})/)?.[1] ?? null;
|
||||
if (left && leftPrefix && KNOWN_ACCOUNT_PREFIXES.has(leftPrefix)) accounts.add(left);
|
||||
if (right && rightPrefix && KNOWN_ACCOUNT_PREFIXES.has(rightPrefix)) accounts.add(right);
|
||||
}
|
||||
const genericAccountPattern = /\b(\d{2}(?:\.\d{2})?)\b/g;
|
||||
let genericMatch: RegExpExecArray | null = null;
|
||||
const classifiedNumericTokens: Array<{
|
||||
token: string;
|
||||
classification: "account_token" | "date_token" | "amount_token" | "percent_token" | "other_numeric";
|
||||
}> = [];
|
||||
const rejectedAsNonAccounts = new Set<string>();
|
||||
while ((genericMatch = genericAccountPattern.exec(lower)) !== null) {
|
||||
const token = String(genericMatch[1] ?? "").trim();
|
||||
const start = genericMatch.index;
|
||||
const end = start + token.length;
|
||||
const prefix = token.match(/^(\d{2})/)?.[1] ?? null;
|
||||
if (options?.forceAccountContext === true && prefix && KNOWN_ACCOUNT_PREFIXES.has(prefix)) {
|
||||
accounts.add(token);
|
||||
classifiedNumericTokens.push({
|
||||
token,
|
||||
classification: "account_token"
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (intersectsSpan(start, end, dateSpans)) {
|
||||
classifiedNumericTokens.push({
|
||||
token,
|
||||
classification: "date_token"
|
||||
});
|
||||
rejectedAsNonAccounts.add(token);
|
||||
continue;
|
||||
}
|
||||
if (intersectsSpan(start, end, amountSpans)) {
|
||||
classifiedNumericTokens.push({
|
||||
token,
|
||||
classification: "amount_token"
|
||||
});
|
||||
rejectedAsNonAccounts.add(token);
|
||||
continue;
|
||||
}
|
||||
if (intersectsSpan(start, end, percentSpans)) {
|
||||
classifiedNumericTokens.push({
|
||||
token,
|
||||
classification: "percent_token"
|
||||
});
|
||||
rejectedAsNonAccounts.add(token);
|
||||
continue;
|
||||
}
|
||||
if (!prefix || !KNOWN_ACCOUNT_PREFIXES.has(prefix)) {
|
||||
classifiedNumericTokens.push({
|
||||
token,
|
||||
classification: "other_numeric"
|
||||
});
|
||||
rejectedAsNonAccounts.add(token);
|
||||
continue;
|
||||
}
|
||||
accounts.add(token);
|
||||
classifiedNumericTokens.push({
|
||||
token,
|
||||
classification: "account_token"
|
||||
});
|
||||
}
|
||||
return Array.from(accounts);
|
||||
const rawNumericTokens = uniqueStrings((lower.match(/\b\d{1,4}(?:[.,]\d{1,4})?\b/g) ?? []).map((item) => String(item)));
|
||||
for (const token of accounts) {
|
||||
if (!classifiedNumericTokens.some((item) => item.token === token && item.classification === "account_token")) {
|
||||
classifiedNumericTokens.push({
|
||||
token,
|
||||
classification: "account_token"
|
||||
});
|
||||
}
|
||||
}
|
||||
// Numeric tokens hidden behind blocked spans still need explicit audit markers.
|
||||
const blockedMatchPattern = /\b\d{2}(?:\.\d{2})?\b/g;
|
||||
let blockedMatch: RegExpExecArray | null = null;
|
||||
while ((blockedMatch = blockedMatchPattern.exec(lower)) !== null) {
|
||||
const token = String(blockedMatch[0] ?? "").trim();
|
||||
const start = blockedMatch.index;
|
||||
const end = start + token.length;
|
||||
if (!intersectsSpan(start, end, blockedSpans)) {
|
||||
continue;
|
||||
}
|
||||
if (classifiedNumericTokens.some((item) => item.token === token)) {
|
||||
continue;
|
||||
}
|
||||
const classification = intersectsSpan(start, end, dateSpans)
|
||||
? "date_token"
|
||||
: intersectsSpan(start, end, amountSpans)
|
||||
? "amount_token"
|
||||
: "percent_token";
|
||||
classifiedNumericTokens.push({
|
||||
token,
|
||||
classification
|
||||
});
|
||||
rejectedAsNonAccounts.add(token);
|
||||
}
|
||||
return {
|
||||
resolved_account_anchors: Array.from(accounts),
|
||||
raw_numeric_tokens: rawNumericTokens,
|
||||
classified_numeric_tokens: classifiedNumericTokens,
|
||||
rejected_as_non_accounts: Array.from(rejectedAsNonAccounts)
|
||||
};
|
||||
}
|
||||
|
||||
function extractAccountsFromUnknown(value: unknown): string[] {
|
||||
function extractAccountsFromText(text: string): string[] {
|
||||
return extractAccountsFromTextDetailed(text).resolved_account_anchors;
|
||||
}
|
||||
|
||||
function extractAccountsFromUnknown(value: unknown, pathKey = ""): string[] {
|
||||
if (Array.isArray(value)) {
|
||||
return uniqueStrings(value.flatMap((item) => extractAccountsFromUnknown(item)));
|
||||
return uniqueStrings(value.flatMap((item) => extractAccountsFromUnknown(item, pathKey)));
|
||||
}
|
||||
if (value && typeof value === "object") {
|
||||
return uniqueStrings(Object.values(value as Record<string, unknown>).flatMap((item) => extractAccountsFromUnknown(item)));
|
||||
return uniqueStrings(
|
||||
Object.entries(value as Record<string, unknown>).flatMap(([key, item]) =>
|
||||
extractAccountsFromUnknown(item, `${pathKey}.${String(key).toLowerCase()}`)
|
||||
)
|
||||
);
|
||||
}
|
||||
if (typeof value !== "string" && typeof value !== "number") {
|
||||
return [];
|
||||
}
|
||||
const text = String(value);
|
||||
const matches = text.match(/\b\d{2}(?:\.\d{2})?\b/g) ?? [];
|
||||
return uniqueStrings(matches);
|
||||
const contextPath = String(pathKey ?? "").toLowerCase();
|
||||
if (
|
||||
contextPath.length > 0 &&
|
||||
!/(?:account|счет|сч|debit|credit|дт|кт|konto|subkonto|analytics|context)/iu.test(contextPath)
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
const forceAccountContext = /(?:account|счет|сч|debit|credit|дт|кт|konto|subkonto|analytics|context)/iu.test(contextPath);
|
||||
return extractAccountsFromTextDetailed(String(value), {
|
||||
forceAccountContext
|
||||
}).resolved_account_anchors;
|
||||
}
|
||||
|
||||
function normalizeTwoDigits(value: string): string {
|
||||
@@ -399,11 +569,16 @@ function resolveJulyAnchor(rawText: string): TemporalAnchorResolution {
|
||||
}
|
||||
|
||||
export type TemporalGuardOutcome = "passed" | "failed_out_of_snapshot_window" | "ambiguous_limited";
|
||||
export type TemporalAlignmentStatus = "aligned" | "corrected" | "conflicting";
|
||||
|
||||
export interface TemporalGuardAudit {
|
||||
raw_time_anchor: string | null;
|
||||
raw_time_scope: string | null;
|
||||
resolved_time_anchor: string | null;
|
||||
resolved_primary_period: TemporalWindow | null;
|
||||
temporal_alignment_status: TemporalAlignmentStatus;
|
||||
temporal_resolution_source: string;
|
||||
temporal_guard_basis: "resolved_primary_period" | "raw_time_scope_unlocked" | "none";
|
||||
temporal_guard_applied: boolean;
|
||||
temporal_guard_outcome: TemporalGuardOutcome;
|
||||
primary_period_window: TemporalWindow | null;
|
||||
@@ -414,6 +589,37 @@ export interface TemporalGuardAudit {
|
||||
reason_codes: string[];
|
||||
}
|
||||
|
||||
function inferPrimaryWindowFromAnchor(anchor: string | null): TemporalWindow | null {
|
||||
const raw = String(anchor ?? "").trim();
|
||||
if (/^\d{4}-\d{2}$/.test(raw)) {
|
||||
return {
|
||||
from: `${raw}-01`,
|
||||
to: `${raw}-31`,
|
||||
granularity: "month"
|
||||
};
|
||||
}
|
||||
const normalized = normalizeEvidenceDate(raw);
|
||||
if (!normalized) {
|
||||
return null;
|
||||
}
|
||||
if (/^\d{4}-\d{2}-\d{2}$/.test(normalized)) {
|
||||
return {
|
||||
from: normalized,
|
||||
to: normalized,
|
||||
granularity: "day"
|
||||
};
|
||||
}
|
||||
const month = normalized.slice(0, 7);
|
||||
if (!/^\d{4}-\d{2}$/.test(month)) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
from: `${month}-01`,
|
||||
to: `${month}-31`,
|
||||
granularity: "month"
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveTemporalGuard(input: {
|
||||
userMessage: string;
|
||||
normalized: NormalizedPayload | null | undefined;
|
||||
@@ -424,10 +630,15 @@ export function resolveTemporalGuard(input: {
|
||||
const normalizedAnchor = normalizedAnchorFromFragments(input.normalized);
|
||||
const reasonCodes: string[] = [];
|
||||
if (!julyAnchor.applyGuard) {
|
||||
const resolvedWindow = inferPrimaryWindowFromAnchor(normalizedAnchor.value);
|
||||
return {
|
||||
raw_time_anchor: julyAnchor.raw,
|
||||
raw_time_scope: normalizedAnchor.value,
|
||||
resolved_time_anchor: normalizedAnchor.value,
|
||||
resolved_primary_period: resolvedWindow,
|
||||
temporal_alignment_status: normalizedAnchor.value ? "aligned" : "conflicting",
|
||||
temporal_resolution_source: normalizedAnchor.source,
|
||||
temporal_guard_basis: normalizedAnchor.value ? "raw_time_scope_unlocked" : "none",
|
||||
temporal_guard_applied: false,
|
||||
temporal_guard_outcome: "passed",
|
||||
primary_period_window: null,
|
||||
@@ -435,23 +646,30 @@ export function resolveTemporalGuard(input: {
|
||||
controlled_temporal_expansion_enabled: false,
|
||||
context_expansion_reasons_allowed: ["prehistory", "carryover", "post_period_closure", "long_running_contract_context"],
|
||||
normalized_anchor_drift_detected: false,
|
||||
reason_codes: []
|
||||
reason_codes: normalizedAnchor.value ? [] : ["missing_resolved_primary_period"]
|
||||
};
|
||||
}
|
||||
let outcome: TemporalGuardOutcome = "passed";
|
||||
let normalizedAnchorDriftDetected = false;
|
||||
let temporalAlignmentStatus: TemporalAlignmentStatus = "aligned";
|
||||
if (normalizedAnchor.value && julyAnchor.window && !isPeriodWithinWindow(normalizedAnchor.value, julyAnchor.window)) {
|
||||
normalizedAnchorDriftDetected = true;
|
||||
temporalAlignmentStatus = "corrected";
|
||||
reasonCodes.push("normalized_anchor_out_of_primary_window_overridden");
|
||||
} else if (!normalizedAnchor.value && !julyAnchor.resolved) {
|
||||
outcome = "ambiguous_limited";
|
||||
temporalAlignmentStatus = "conflicting";
|
||||
reasonCodes.push("missing_time_anchor_under_snapshot_lock");
|
||||
}
|
||||
const allowedContextWindow = buildAllowedContextWindow(julyAnchor.window);
|
||||
return {
|
||||
raw_time_anchor: julyAnchor.raw,
|
||||
raw_time_scope: normalizedAnchor.value,
|
||||
resolved_time_anchor: julyAnchor.resolved ?? normalizedAnchor.value,
|
||||
resolved_primary_period: julyAnchor.window,
|
||||
temporal_alignment_status: temporalAlignmentStatus,
|
||||
temporal_resolution_source: julyAnchor.source,
|
||||
temporal_guard_basis: julyAnchor.window ? "resolved_primary_period" : "none",
|
||||
temporal_guard_applied: true,
|
||||
temporal_guard_outcome: outcome,
|
||||
primary_period_window: julyAnchor.window,
|
||||
@@ -501,6 +719,13 @@ export interface DomainPolarityGuardAudit {
|
||||
supplier_score: number;
|
||||
customer_score: number;
|
||||
account_scope: string[];
|
||||
raw_numeric_tokens: string[];
|
||||
classified_numeric_tokens: Array<{
|
||||
token: string;
|
||||
classification: "account_token" | "date_token" | "amount_token" | "percent_token" | "other_numeric";
|
||||
}>;
|
||||
rejected_as_non_accounts: string[];
|
||||
resolved_account_anchors: string[];
|
||||
rejected_problem_units: number;
|
||||
rejected_evidence: number;
|
||||
critical_contradiction: boolean;
|
||||
@@ -513,7 +738,8 @@ export function resolveDomainPolarityGuard(input: {
|
||||
focusDomainHint?: string | null;
|
||||
}): DomainPolarityGuardAudit {
|
||||
const lower = String(input.userMessage ?? "").toLowerCase();
|
||||
const accounts = uniqueStrings([...(input.companyAnchors?.accounts ?? []), ...extractAccountsFromText(lower)]);
|
||||
const accountExtraction = extractAccountsFromTextDetailed(lower);
|
||||
const accounts = uniqueStrings([...(input.companyAnchors?.accounts ?? []), ...accountExtraction.resolved_account_anchors]);
|
||||
const prefixes = new Set(accounts.map((item) => accountPrefix(item)).filter((item): item is string => Boolean(item)));
|
||||
const settlementSignal =
|
||||
input.focusDomainHint === "settlements_60_62" ||
|
||||
@@ -530,6 +756,10 @@ export function resolveDomainPolarityGuard(input: {
|
||||
supplier_score: 0,
|
||||
customer_score: 0,
|
||||
account_scope: accounts,
|
||||
raw_numeric_tokens: accountExtraction.raw_numeric_tokens,
|
||||
classified_numeric_tokens: accountExtraction.classified_numeric_tokens,
|
||||
rejected_as_non_accounts: accountExtraction.rejected_as_non_accounts,
|
||||
resolved_account_anchors: accounts,
|
||||
rejected_problem_units: 0,
|
||||
rejected_evidence: 0,
|
||||
critical_contradiction: false,
|
||||
@@ -559,6 +789,10 @@ export function resolveDomainPolarityGuard(input: {
|
||||
supplier_score: supplierScore,
|
||||
customer_score: customerScore,
|
||||
account_scope: accounts,
|
||||
raw_numeric_tokens: accountExtraction.raw_numeric_tokens,
|
||||
classified_numeric_tokens: accountExtraction.classified_numeric_tokens,
|
||||
rejected_as_non_accounts: accountExtraction.rejected_as_non_accounts,
|
||||
resolved_account_anchors: accounts,
|
||||
rejected_problem_units: 0,
|
||||
rejected_evidence: 0,
|
||||
critical_contradiction: unresolved,
|
||||
@@ -1146,6 +1380,8 @@ export function applyEvidenceAdmissibilityGate(input: {
|
||||
export interface GroundedAnswerEligibilityAudit {
|
||||
eligible: boolean;
|
||||
temporal_passed: boolean;
|
||||
eligibility_time_basis: "resolved_primary_period" | "raw_time_scope_unlocked" | "none";
|
||||
business_scope_passed: boolean;
|
||||
polarity_passed: boolean;
|
||||
claim_anchors_passed: boolean;
|
||||
claim_anchor_resolution_rate: number | null;
|
||||
@@ -1163,8 +1399,15 @@ export function evaluateGroundedAnswerEligibility(input: {
|
||||
evidence: EvidenceAdmissibilityAudit;
|
||||
claimAnchors?: ClaimBoundAnchorAudit | null;
|
||||
targetedEvidenceHitRate?: number | null;
|
||||
businessScopeResolved?: string[] | null;
|
||||
}): GroundedAnswerEligibilityAudit {
|
||||
const temporalPassed = input.temporal.temporal_guard_outcome === "passed";
|
||||
const eligibilityTimeBasis = input.temporal.temporal_guard_basis;
|
||||
const scopeValues = Array.isArray(input.businessScopeResolved) ? input.businessScopeResolved : [];
|
||||
const hasCompanyScope = scopeValues.includes("company_specific_accounting");
|
||||
const hasOnlyGenericScope =
|
||||
scopeValues.length > 0 && scopeValues.every((item) => String(item ?? "").trim() === "generic_accounting");
|
||||
const businessScopePassed = scopeValues.length === 0 ? true : hasCompanyScope || !hasOnlyGenericScope;
|
||||
const polarityPassed =
|
||||
!input.polarity.applied || input.polarity.outcome === "passed" || input.polarity.outcome === "not_applicable";
|
||||
const claimAnchorResolutionRate = input.claimAnchors ? Number(input.claimAnchors.claim_anchor_resolution_rate ?? 0) : null;
|
||||
@@ -1182,6 +1425,7 @@ export function evaluateGroundedAnswerEligibility(input: {
|
||||
: Number(input.targetedEvidenceHitRate) > 0;
|
||||
const eligible =
|
||||
temporalPassed &&
|
||||
businessScopePassed &&
|
||||
polarityPassed &&
|
||||
claimAnchorsPassed &&
|
||||
admissibleEvidenceCount > 0 &&
|
||||
@@ -1194,6 +1438,9 @@ export function evaluateGroundedAnswerEligibility(input: {
|
||||
if (!polarityPassed) {
|
||||
reasonCodes.push(`polarity_guard_${input.polarity.outcome}`);
|
||||
}
|
||||
if (!businessScopePassed) {
|
||||
reasonCodes.push("business_scope_generic_unresolved");
|
||||
}
|
||||
if (!claimAnchorsPassed) {
|
||||
reasonCodes.push("claim_anchor_coverage_insufficient");
|
||||
}
|
||||
@@ -1209,6 +1456,8 @@ export function evaluateGroundedAnswerEligibility(input: {
|
||||
return {
|
||||
eligible,
|
||||
temporal_passed: temporalPassed,
|
||||
eligibility_time_basis: eligibilityTimeBasis,
|
||||
business_scope_passed: businessScopePassed,
|
||||
polarity_passed: polarityPassed,
|
||||
claim_anchors_passed: claimAnchorsPassed,
|
||||
claim_anchor_resolution_rate: claimAnchorResolutionRate,
|
||||
@@ -1229,7 +1478,10 @@ export function applyEligibilityToGroundingCheck<T extends { status: string; rea
|
||||
return groundingCheck;
|
||||
}
|
||||
const status =
|
||||
eligibility.admissible_evidence_count <= 0 || !eligibility.temporal_passed || !eligibility.claim_anchors_passed
|
||||
eligibility.admissible_evidence_count <= 0 ||
|
||||
!eligibility.temporal_passed ||
|
||||
!eligibility.claim_anchors_passed ||
|
||||
!eligibility.business_scope_passed
|
||||
? "no_grounded_answer"
|
||||
: "partial";
|
||||
const reasonMap: Record<string, string> = {
|
||||
@@ -1237,6 +1489,7 @@ export function applyEligibilityToGroundingCheck<T extends { status: string; rea
|
||||
critical_domain_or_account_contradiction: "Есть критическое противоречие по domain/account scope.",
|
||||
temporal_guard_failed_out_of_snapshot_window: "Temporal anchor вышел за окно company snapshot (июль 2020).",
|
||||
temporal_guard_ambiguous_limited: "Temporal anchor не разрешен надежно в пределах company snapshot.",
|
||||
business_scope_generic_unresolved: "Business scope остался generic и не подтвержден как company-specific для доказательного ответа.",
|
||||
polarity_guard_limited_unresolved_polarity: "Не удалось надежно определить supplier/customer polarity.",
|
||||
polarity_guard_blocked_conflict: "Обнаружен конфликт supplier/customer polarity в retrieval-контуре.",
|
||||
claim_anchor_coverage_insufficient: "Недостаточно покрытия required anchors для claim-bound grounding.",
|
||||
|
||||
Reference in New Issue
Block a user