ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Рефакторинг этапов 2.2.1 - фикс деградаций по старым доменам + легкая доводка регрессий перед стартом 3его этапа
This commit is contained in:
@@ -1854,6 +1854,146 @@ function buildAnswerSummary(mode: PolicyMode): string {
|
||||
|
||||
type P0NarrativeDomain = "settlements_60_62" | "vat_document_register_book" | "month_close_costs_20_44" | null;
|
||||
|
||||
interface BoundaryCapabilitySuggestion {
|
||||
key: "settlements_60_62" | "vat_document_register_book" | "month_close_costs_20_44";
|
||||
label: string;
|
||||
helpText: string;
|
||||
signals: RegExp;
|
||||
}
|
||||
|
||||
const BOUNDARY_CAPABILITY_SUGGESTIONS: BoundaryCapabilitySuggestion[] = [
|
||||
{
|
||||
key: "settlements_60_62",
|
||||
label: "Взаиморасчеты 60/62",
|
||||
helpText: "найти хвосты, незакрытые оплаты и рисковые связки по контрагентам.",
|
||||
signals: /(контраг|долг|сальдо|взаиморасчет|оплат|аванс|покупат|поставщ|банк|выписк|\b60\b|\b62\b|\b76\b)/iu
|
||||
},
|
||||
{
|
||||
key: "vat_document_register_book",
|
||||
label: "НДС 19/68",
|
||||
helpText: "проверить цепочку документ -> счет-фактура -> регистр -> книга.",
|
||||
signals: /(ндс|сч[её]т[-\s]?фактур|регистр|книга\s+покуп|книга\s+продаж|декларац|\b19\b|\b68\b)/iu
|
||||
},
|
||||
{
|
||||
key: "month_close_costs_20_44",
|
||||
label: "Закрытие месяца 20/44",
|
||||
helpText: "проверить распределение затрат и остатки после регламентных операций.",
|
||||
signals: /(закрыти[ея]|месяц|затрат|распределени|рбп|аморт|основн|ос\b|\b20\b|\b25\b|\b26\b|\b44\b)/iu
|
||||
}
|
||||
];
|
||||
|
||||
function formatNarrativeDomainLabel(domain: P0NarrativeDomain): string {
|
||||
if (domain === "settlements_60_62") {
|
||||
return "взаиморасчетов 60/62";
|
||||
}
|
||||
if (domain === "vat_document_register_book") {
|
||||
return "НДС-контура 19/68";
|
||||
}
|
||||
if (domain === "month_close_costs_20_44") {
|
||||
return "закрытия месяца (20/44)";
|
||||
}
|
||||
return "доступного учетного контура";
|
||||
}
|
||||
|
||||
function pickBoundaryCapabilityLines(userMessage: string, limit = 3): string[] {
|
||||
const text = String(userMessage ?? "").toLowerCase();
|
||||
const scored = BOUNDARY_CAPABILITY_SUGGESTIONS.map((item, index) => ({
|
||||
item,
|
||||
score: (text.match(item.signals) ?? []).length,
|
||||
order: index
|
||||
}));
|
||||
const ranked = scored
|
||||
.slice()
|
||||
.sort((left, right) => right.score - left.score || left.order - right.order)
|
||||
.map((entry) => entry.item);
|
||||
const selected = ranked.slice(0, Math.max(2, limit));
|
||||
return uniqueStrings(selected.map((item) => `${item.label}: ${item.helpText}`), limit);
|
||||
}
|
||||
|
||||
function buildNaturalClarificationHints(input: {
|
||||
missingAnchors: MissingAnchors;
|
||||
coverageReport: RequirementCoverageReport;
|
||||
}): string[] {
|
||||
const hints: string[] = [];
|
||||
if (input.missingAnchors.period) {
|
||||
hints.push("Укажи период проверки (например, июль 2020).");
|
||||
}
|
||||
if (input.missingAnchors.account) {
|
||||
hints.push("Укажи счет или связку счетов (например, 60/62, 19/68 или 20/44).");
|
||||
}
|
||||
if (input.missingAnchors.counterparty) {
|
||||
hints.push("Добавь контрагента или договор, чтобы зафиксировать контур проверки.");
|
||||
}
|
||||
if (input.missingAnchors.documentOrObject) {
|
||||
hints.push("Укажи документ или объект, от которого строить проверку цепочки.");
|
||||
}
|
||||
if (input.missingAnchors.anomalyType) {
|
||||
hints.push("Уточни тип отклонения: разрыв цепочки, неверное закрытие или аномальный хвост.");
|
||||
}
|
||||
if (input.coverageReport.clarification_needed_for.length > 0) {
|
||||
hints.push(`Закрой уточнения для требований: ${input.coverageReport.clarification_needed_for.join(", ")}.`);
|
||||
}
|
||||
return uniqueStrings(hints, 5);
|
||||
}
|
||||
|
||||
function shouldUseBoundaryFallbackReply(input: {
|
||||
mode: PolicyMode;
|
||||
groundingCheck: AnswerGroundingCheck;
|
||||
coverageReport: RequirementCoverageReport;
|
||||
okResultsCount: number;
|
||||
partialResultsCount: number;
|
||||
}): boolean {
|
||||
if (input.mode === "out_of_scope") {
|
||||
return true;
|
||||
}
|
||||
if (input.mode !== "clarification_required" && input.mode !== "no_grounded") {
|
||||
return false;
|
||||
}
|
||||
const hasNoEvidenceRoutes = input.okResultsCount === 0 && input.partialResultsCount === 0;
|
||||
const hasNoConfirmedCoverage =
|
||||
input.coverageReport.requirements_covered === 0 &&
|
||||
input.coverageReport.requirements_partially_covered.length === 0;
|
||||
const groundingBlocked =
|
||||
input.groundingCheck.status === "no_grounded_answer" ||
|
||||
input.groundingCheck.status === "partial" ||
|
||||
input.groundingCheck.status === "route_mismatch_blocked";
|
||||
return hasNoEvidenceRoutes && hasNoConfirmedCoverage && groundingBlocked;
|
||||
}
|
||||
|
||||
function buildBoundaryFallbackReply(input: {
|
||||
userMessage: string;
|
||||
focusDomain: P0NarrativeDomain;
|
||||
missingAnchors: MissingAnchors;
|
||||
coverageReport: RequirementCoverageReport;
|
||||
}): string {
|
||||
const nearbyCapabilities = pickBoundaryCapabilityLines(input.userMessage, 3);
|
||||
if (input.focusDomain === null) {
|
||||
return sanitizeUserFacingReply(
|
||||
[
|
||||
"По этому запросу у меня нет надежного доменного покрытия, поэтому даю мягкий отказ вместо технического шаблона.",
|
||||
nearbyCapabilities.length > 0 ? `Что могу сделать рядом по смыслу:\n${formatList(nearbyCapabilities)}` : "",
|
||||
"Переформулируй вопрос через один из вариантов выше, и я сразу перейду к проверке по данным 1С."
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n\n")
|
||||
);
|
||||
}
|
||||
|
||||
const clarificationHints = buildNaturalClarificationHints({
|
||||
missingAnchors: input.missingAnchors,
|
||||
coverageReport: input.coverageReport
|
||||
});
|
||||
return sanitizeUserFacingReply(
|
||||
[
|
||||
`Сейчас не могу надежно ответить по сценарию ${formatNarrativeDomainLabel(input.focusDomain)}: не хватает опоры.`,
|
||||
clarificationHints.length > 0 ? `Чтобы сразу перейти к проверке, уточни:\n${formatList(clarificationHints)}` : "",
|
||||
nearbyCapabilities.length > 0 ? `Если удобнее, могу начать с близкого сценария:\n${formatList(nearbyCapabilities.slice(0, 2))}` : ""
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n\n")
|
||||
);
|
||||
}
|
||||
|
||||
function ensureSentence(value: string): string {
|
||||
const sanitized = sanitizeUserText(value) ?? String(value ?? "").trim();
|
||||
const normalized = sanitized.replace(/\s+/g, " ").trim();
|
||||
@@ -4219,6 +4359,13 @@ function composeAssistantAnswerV11(input: ComposeAnswerInput): ComposeAnswerOutp
|
||||
normalizationPeriodExplicit: Boolean(input.normalizationPeriodExplicit),
|
||||
companyAnchors: input.companyAnchors ?? null
|
||||
});
|
||||
const useBoundaryFallbackReply = shouldUseBoundaryFallbackReply({
|
||||
mode: guardedDecision.mode,
|
||||
groundingCheck: input.groundingCheck,
|
||||
coverageReport: input.coverageReport,
|
||||
okResultsCount: okResults.length,
|
||||
partialResultsCount: partialResults.length
|
||||
});
|
||||
const hasProblemWeakSignal =
|
||||
policySignals.narrowing_strength !== "strong" ||
|
||||
policySignals.minimum_evidence_failed ||
|
||||
@@ -4238,6 +4385,7 @@ function composeAssistantAnswerV11(input: ComposeAnswerInput): ComposeAnswerOutp
|
||||
(guardedDecision.mode === "focused_grounded" && hasProblemWeakSignal);
|
||||
const shouldUseProblemCentricAnswer =
|
||||
Boolean(input.enableProblemCentricAnswerV1) &&
|
||||
!useBoundaryFallbackReply &&
|
||||
!hardBlockedMode &&
|
||||
problemCentricModeEligible &&
|
||||
(!focusedStrong || hasProblemWeakSignal) &&
|
||||
@@ -4382,13 +4530,22 @@ function composeAssistantAnswerV11(input: ComposeAnswerInput): ComposeAnswerOutp
|
||||
}
|
||||
};
|
||||
|
||||
const finalAssistantReply = useBoundaryFallbackReply
|
||||
? buildBoundaryFallbackReply({
|
||||
userMessage: input.userMessage,
|
||||
focusDomain: focusNarrativeDomain,
|
||||
missingAnchors,
|
||||
coverageReport: input.coverageReport
|
||||
})
|
||||
: renderPolicyReply(answerStructure, {
|
||||
questionType,
|
||||
focusDomain: focusNarrativeDomain,
|
||||
anchors: anchorUsage,
|
||||
userMessage: input.userMessage
|
||||
});
|
||||
|
||||
return {
|
||||
assistant_reply: renderPolicyReply(answerStructure, {
|
||||
questionType,
|
||||
focusDomain: focusNarrativeDomain,
|
||||
anchors: anchorUsage,
|
||||
userMessage: input.userMessage
|
||||
}),
|
||||
assistant_reply: finalAssistantReply,
|
||||
fallback_type: guardedDecision.fallback_type,
|
||||
reply_type: guardedDecision.reply_type,
|
||||
answer_structure_v11: answerStructure,
|
||||
|
||||
Reference in New Issue
Block a user