ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Рефакторинг этапов 2.2 - Усилена оркестрацию в deep/address гейте и follow-up binding в assistantService.ts. Починен кейс UTF-8 follow-up refinement (теперь followup_state_usage.applied=true в нужном сценарии). Убраны регрессии по assistantLivingRouter и stage3 lifecycle probe. Корректное поведение для llm canonical candidate (чтобы не уезжало в clarification_required там, где должен быть address factual).
This commit is contained in:
@@ -4,6 +4,7 @@ import type { CompanyAnchorSet } from "./companyAnchorResolver";
|
||||
import type { EvidenceItem } from "../types/stage1Contracts";
|
||||
import type { ProblemUnit } from "../types/stage2ProblemUnits";
|
||||
import type { ClaimBoundAnchorAudit } from "./assistantClaimBoundEvidence";
|
||||
import iconv from "iconv-lite";
|
||||
|
||||
type P0DomainHint =
|
||||
| "settlements_60_62"
|
||||
@@ -895,12 +896,70 @@ export interface DomainPolarityGuardAudit {
|
||||
reason_codes: string[];
|
||||
}
|
||||
|
||||
function mojibakeScoreForRuntimeGuards(value: string): number {
|
||||
const source = String(value ?? "");
|
||||
const cyrillic = (source.match(/[А-Яа-яЁё]/g) ?? []).length;
|
||||
const latin = (source.match(/[A-Za-z]/g) ?? []).length;
|
||||
const hardMarkers = (source.match(/[ѓ“‚„…†‡€‰‹‰ЉЊ‹Џ‘’“”•–—™љ›њћџ]/g) ?? []).length;
|
||||
const pairMarkers = (source.match(/(?:Р.|С.|Гђ.|Г‘.)/g) ?? []).length;
|
||||
const doubleEncodedMarkers = (source.match(/(?:Р“.|Р’.|Гѓ.|Г‚.)/gu) ?? []).length;
|
||||
return cyrillic + latin - hardMarkers * 3 - pairMarkers * 2 - doubleEncodedMarkers * 2;
|
||||
}
|
||||
|
||||
function looksLikeMojibakeForRuntimeGuards(value: string): boolean {
|
||||
const source = String(value ?? "");
|
||||
if (!source.trim()) {
|
||||
return false;
|
||||
}
|
||||
if (/[ѓ“‚„…†‡€‰‹‰ЉЊ‹Џ‘’“”•–—™љ›њћџ]/.test(source)) {
|
||||
return true;
|
||||
}
|
||||
if ((source.match(/(?:Р.|С.|Гђ.|Г‘.)/g) ?? []).length >= 2) {
|
||||
return true;
|
||||
}
|
||||
return (source.match(/(?:Р“.|Р’.|Гѓ.|Г‚.)/gu) ?? []).length >= 2;
|
||||
}
|
||||
|
||||
function repairRuntimeGuardsMojibake(value: string): string {
|
||||
const source = String(value ?? "");
|
||||
if (!looksLikeMojibakeForRuntimeGuards(source)) {
|
||||
return source;
|
||||
}
|
||||
let candidate = source;
|
||||
for (let pass = 0; pass < 3; pass += 1) {
|
||||
let improved = false;
|
||||
try {
|
||||
const fromWin1251 = iconv.encode(candidate, "win1251").toString("utf8");
|
||||
if (mojibakeScoreForRuntimeGuards(fromWin1251) > mojibakeScoreForRuntimeGuards(candidate)) {
|
||||
candidate = fromWin1251;
|
||||
improved = true;
|
||||
}
|
||||
} catch (_error) {
|
||||
// noop
|
||||
}
|
||||
try {
|
||||
const fromLatin1 = Buffer.from(candidate, "latin1").toString("utf8");
|
||||
if (mojibakeScoreForRuntimeGuards(fromLatin1) > mojibakeScoreForRuntimeGuards(candidate)) {
|
||||
candidate = fromLatin1;
|
||||
improved = true;
|
||||
}
|
||||
} catch (_error) {
|
||||
// noop
|
||||
}
|
||||
if (!improved) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
|
||||
export function resolveDomainPolarityGuard(input: {
|
||||
userMessage: string;
|
||||
companyAnchors?: CompanyAnchorSet | null;
|
||||
focusDomainHint?: string | null;
|
||||
}): DomainPolarityGuardAudit {
|
||||
const lower = String(input.userMessage ?? "").toLowerCase();
|
||||
const repairedMessage = repairRuntimeGuardsMojibake(String(input.userMessage ?? ""));
|
||||
const lower = repairedMessage.toLowerCase();
|
||||
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)));
|
||||
@@ -1683,7 +1742,8 @@ export function applyEligibilityToGroundingCheck<T extends { status: string; rea
|
||||
const reasonMap: Record<string, string> = {
|
||||
admissible_evidence_count_zero: "Недостаточно подтвержденных данных для уверенного ответа.",
|
||||
critical_domain_or_account_contradiction: "Есть противоречие по выбранному домену или контуру счета.",
|
||||
temporal_guard_failed_out_of_snapshot_window: "Запрошенный период выходит за доступный срез данных.",
|
||||
temporal_guard_failed_out_of_snapshot_window:
|
||||
"Запрошенный период выходит за доступный срез данных. Temporal anchor outside snapshot window.",
|
||||
temporal_guard_ambiguous_limited: "Период в вопросе определен недостаточно точно.",
|
||||
business_scope_generic_unresolved: "Не удалось надежно привязать вопрос к конкретному бизнес-контексту.",
|
||||
polarity_guard_limited_unresolved_polarity: "Не удалось однозначно определить сторону расчета (нам должны или мы должны).",
|
||||
|
||||
Reference in New Issue
Block a user