Этап 4 / Волна 18 закрытие блокеров по времени, доменной полярности и допуску доказательной базы

This commit is contained in:
2026-03-29 00:40:06 +03:00
parent 7eb1410501
commit d7e145010b
140 changed files with 417053 additions and 42196 deletions
@@ -0,0 +1,887 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveTemporalGuard = resolveTemporalGuard;
exports.applyTemporalHintToExecutionPlan = applyTemporalHintToExecutionPlan;
exports.resolveDomainPolarityGuard = resolveDomainPolarityGuard;
exports.applyPolarityHintToExecutionPlan = applyPolarityHintToExecutionPlan;
exports.applyDomainPolarityGuardToRetrievalResults = applyDomainPolarityGuardToRetrievalResults;
exports.applyEvidenceAdmissibilityGate = applyEvidenceAdmissibilityGate;
exports.evaluateGroundedAnswerEligibility = evaluateGroundedAnswerEligibility;
exports.applyEligibilityToGroundingCheck = applyEligibilityToGroundingCheck;
const JULY_YEAR = "2020";
const JULY_MONTH = "07";
const JULY_WINDOW = {
from: "2020-07-01",
to: "2020-07-31",
granularity: "month"
};
const RUS_MONTH_TO_NUMBER = {
января: "01",
январь: "01",
февраля: "02",
февраль: "02",
марта: "03",
март: "03",
апреля: "04",
апрель: "04",
мая: "05",
май: "05",
июня: "06",
июнь: "06",
июля: "07",
июль: "07",
августа: "08",
август: "08",
сентября: "09",
сентябрь: "09",
октября: "10",
октябрь: "10",
ноября: "11",
ноябрь: "11",
декабря: "12",
декабрь: "12"
};
function uniqueStrings(values) {
return Array.from(new Set(values.map((item) => String(item ?? "").trim()).filter(Boolean)));
}
function toObject(value) {
if (!value || typeof value !== "object" || Array.isArray(value)) {
return null;
}
return value;
}
function toObjectArray(value) {
if (!Array.isArray(value)) {
return [];
}
return value.filter((item) => Boolean(item) && typeof item === "object");
}
function accountPrefix(value) {
const token = String(value ?? "").trim();
const match = token.match(/^(\d{2})/);
return match ? match[1] : null;
}
function extractAccountsFromText(text) {
const lower = String(text ?? "").toLowerCase();
const accounts = new Set();
const contextualPattern = /(?:\b(?:сч(?:е|ё)т(?:а|у|ом|ов)?|account|schet)\b\s*(?:№|#|:)?\s*)(\d{2}(?:\.\d{2})?)/giu;
let contextualMatch = null;
while ((contextualMatch = contextualPattern.exec(lower)) !== null) {
const token = String(contextualMatch[1] ?? "").trim();
if (token) {
accounts.add(token);
}
}
const pairPattern = /\b(\d{2}\.\d{2})\s*\/\s*(\d{2}\.\d{2})\b/g;
let pairMatch = null;
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);
}
return Array.from(accounts);
}
function extractAccountsFromUnknown(value) {
if (Array.isArray(value)) {
return uniqueStrings(value.flatMap((item) => extractAccountsFromUnknown(item)));
}
if (value && typeof value === "object") {
return uniqueStrings(Object.values(value).flatMap((item) => extractAccountsFromUnknown(item)));
}
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);
}
function normalizeTwoDigits(value) {
return String(value).padStart(2, "0");
}
function parseYear(raw) {
const token = String(raw ?? "").trim();
if (token.length === 2) {
return `20${token}`;
}
return token;
}
function normalizeDateIso(input) {
const year = String(input.year ?? "").trim();
const month = normalizeTwoDigits(input.month ?? "");
if (!/^\d{4}$/.test(year) || !/^\d{2}$/.test(month)) {
return null;
}
if (input.day === undefined || input.day === null || String(input.day).trim().length === 0) {
return `${year}-${month}`;
}
const day = normalizeTwoDigits(input.day ?? "");
if (!/^\d{2}$/.test(day)) {
return null;
}
return `${year}-${month}-${day}`;
}
function parseDateLike(raw) {
const value = String(raw ?? "").trim().toLowerCase();
if (!value) {
return null;
}
const isoDay = value.match(/\b(20\d{2})[-/.](0[1-9]|1[0-2])[-/.](0[1-9]|[12]\d|3[01])\b/);
if (isoDay) {
return normalizeDateIso({ year: isoDay[1], month: isoDay[2], day: isoDay[3] });
}
const isoMonth = value.match(/\b(20\d{2})[-/.](0[1-9]|1[0-2])\b/);
if (isoMonth) {
return normalizeDateIso({ year: isoMonth[1], month: isoMonth[2] });
}
const dayMonthYear = value.match(/\b(0?[1-9]|[12]\d|3[01])[./-](0?[1-9]|1[0-2])[./-](\d{2}|\d{4})\b/);
if (dayMonthYear) {
return normalizeDateIso({ year: parseYear(dayMonthYear[3]), month: dayMonthYear[2], day: dayMonthYear[1] });
}
const rusMonthYear = value.match(/\b(январь|февраль|март|апрель|май|июнь|июль|август|сентябрь|октябрь|ноябрь|декабрь)\s+(20\d{2})\b/i);
if (rusMonthYear) {
const month = RUS_MONTH_TO_NUMBER[String(rusMonthYear[1] ?? "").toLowerCase()];
if (!month)
return null;
return normalizeDateIso({ year: rusMonthYear[2], month });
}
return null;
}
function monthStart(iso) {
const month = String(iso ?? "").slice(0, 7);
if (!/^\d{4}-\d{2}$/.test(month)) {
return null;
}
return `${month}-01`;
}
function normalizeEvidenceDate(value) {
const parsed = parseDateLike(value);
if (!parsed) {
return null;
}
if (/^\d{4}-\d{2}-\d{2}$/.test(parsed)) {
return parsed;
}
if (/^\d{4}-\d{2}$/.test(parsed)) {
return monthStart(parsed);
}
return null;
}
function isPeriodWithinWindow(periodIso, window) {
const normalized = normalizeEvidenceDate(periodIso);
if (!normalized) {
return false;
}
return normalized >= window.from && normalized <= window.to;
}
function extractNormalizedFragments(normalized) {
if (!normalized || typeof normalized !== "object") {
return [];
}
const source = normalized;
return toObjectArray(source.fragments);
}
function normalizedAnchorFromFragments(normalized) {
const fragments = extractNormalizedFragments(normalized);
for (const fragment of fragments) {
const timeScope = toObject(fragment.time_scope);
const type = String(timeScope?.type ?? "").trim().toLowerCase();
const value = String(timeScope?.value ?? "").trim();
if (!value) {
continue;
}
const parsed = parseDateLike(value);
if (parsed) {
return {
value: parsed,
source: `normalized_time_scope:${type || "unknown"}`
};
}
if (/(?:июл|july)/i.test(value)) {
return {
value: `${JULY_YEAR}-${JULY_MONTH}`,
source: `normalized_time_scope:${type || "unknown"}`
};
}
}
return {
value: null,
source: "normalized_time_scope:missing"
};
}
function collectRawTemporalAnchorText(userMessage, companyAnchors) {
return [userMessage, ...(companyAnchors?.periods ?? []), ...(companyAnchors?.dates ?? [])]
.map((item) => String(item ?? "").trim())
.filter(Boolean)
.join(" ");
}
function resolveJulyAnchor(rawText) {
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 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);
if (!dayByNamedJuly && !dayByNumeric && !monthByNamed && !monthByNumeric) {
return {
raw: null,
resolved: null,
source: "no_july_anchor",
window: null,
applyGuard: false
};
}
const dayValue = dayByNamedJuly?.[1] ?? dayByNumeric?.[1] ?? null;
const explicitDayYear = dayByNumeric?.[3] ? parseYear(dayByNumeric[3]) : null;
const anchorYear = explicitDayYear ?? explicitYear ?? JULY_YEAR;
const applyGuard = anchorYear === JULY_YEAR;
if (!applyGuard) {
return {
raw: dayByNamedJuly?.[0] ?? dayByNumeric?.[0] ?? (monthByNamed ? "июль" : "07"),
resolved: normalizeDateIso({
year: anchorYear,
month: JULY_MONTH,
...(dayValue ? { day: dayValue } : {})
}),
source: "explicit_non_snapshot_year",
window: null,
applyGuard: false
};
}
if (dayValue) {
const dayIso = normalizeDateIso({
year: JULY_YEAR,
month: JULY_MONTH,
day: dayValue
});
if (dayIso) {
return {
raw: dayByNamedJuly?.[0] ?? dayByNumeric?.[0] ?? null,
resolved: dayIso,
source: "company_snapshot_july_day_lock",
window: {
from: dayIso,
to: dayIso,
granularity: "day"
},
applyGuard: true
};
}
}
return {
raw: monthByNamed ? "июль" : "2020-07",
resolved: `${JULY_YEAR}-${JULY_MONTH}`,
source: "company_snapshot_july_month_lock",
window: JULY_WINDOW,
applyGuard: true
};
}
function resolveTemporalGuard(input) {
const rawAnchorText = collectRawTemporalAnchorText(input.userMessage, input.companyAnchors);
const julyAnchor = resolveJulyAnchor(rawAnchorText);
const normalizedAnchor = normalizedAnchorFromFragments(input.normalized);
const reasonCodes = [];
if (!julyAnchor.applyGuard) {
return {
raw_time_anchor: julyAnchor.raw,
resolved_time_anchor: normalizedAnchor.value,
temporal_resolution_source: normalizedAnchor.source,
temporal_guard_applied: false,
temporal_guard_outcome: "passed",
primary_period_window: null,
reason_codes: []
};
}
let outcome = "passed";
if (normalizedAnchor.value && julyAnchor.window && !isPeriodWithinWindow(normalizedAnchor.value, julyAnchor.window)) {
outcome = "failed_out_of_snapshot_window";
reasonCodes.push("normalized_anchor_out_of_snapshot_window");
}
else if (!normalizedAnchor.value && !julyAnchor.resolved) {
outcome = "ambiguous_limited";
reasonCodes.push("missing_time_anchor_under_snapshot_lock");
}
return {
raw_time_anchor: julyAnchor.raw,
resolved_time_anchor: julyAnchor.resolved ?? normalizedAnchor.value,
temporal_resolution_source: julyAnchor.source,
temporal_guard_applied: true,
temporal_guard_outcome: outcome,
primary_period_window: julyAnchor.window,
reason_codes: reasonCodes
};
}
function applyTemporalHintToExecutionPlan(executionPlan, temporal) {
if (!temporal.temporal_guard_applied) {
return executionPlan;
}
const hint = temporal.primary_period_window?.granularity === "day" && temporal.resolved_time_anchor
? `в рамках company snapshot даты ${temporal.resolved_time_anchor}`
: `в рамках company snapshot июля 2020 (${JULY_WINDOW.from}..${JULY_WINDOW.to})`;
return executionPlan.map((item) => {
if (!item.should_execute) {
return item;
}
const text = String(item.fragment_text ?? "").trim();
if (/2020-07|июл|july/i.test(text)) {
return item;
}
return {
...item,
fragment_text: `${text}; ${hint}`.trim()
};
});
}
function resolveDomainPolarityGuard(input) {
const lower = String(input.userMessage ?? "").toLowerCase();
const accounts = uniqueStrings([...(input.companyAnchors?.accounts ?? []), ...extractAccountsFromText(lower)]);
const prefixes = new Set(accounts.map((item) => accountPrefix(item)).filter((item) => Boolean(item)));
const settlementSignal = input.focusDomainHint === "settlements_60_62" ||
prefixes.has("60") ||
prefixes.has("62") ||
prefixes.has("51") ||
prefixes.has("76") ||
/(?:расч[её]т|оплат|аванс|долг|settlement|payment|tail|хвост|незакры|зач[её]т)/i.test(lower);
if (!settlementSignal) {
return {
applied: false,
polarity: "not_applicable",
outcome: "not_applicable",
supplier_score: 0,
customer_score: 0,
account_scope: accounts,
rejected_problem_units: 0,
rejected_evidence: 0,
critical_contradiction: false,
reason_codes: []
};
}
const supplierScore = (/(?:поставщ|supplier|vendor|кредитор|обязательств|payable)/i.test(lower) ? 2 : 0) +
(prefixes.has("60") ? 2 : 0) +
(/(?:счет\s*60|по\s*60)/i.test(lower) ? 1 : 0);
const customerScore = (/(?:покупат|customer|buyer|дебитор|receivable)/i.test(lower) ? 2 : 0) +
(prefixes.has("62") ? 2 : 0) +
(/(?:счет\s*62|по\s*62)/i.test(lower) ? 1 : 0);
let polarity = "mixed_or_unresolved";
if (supplierScore > 0 && customerScore === 0) {
polarity = "supplier_payable";
}
else if (customerScore > 0 && supplierScore === 0) {
polarity = "customer_receivable";
}
const unresolved = polarity === "mixed_or_unresolved";
return {
applied: true,
polarity,
outcome: unresolved ? "limited_unresolved_polarity" : "passed",
supplier_score: supplierScore,
customer_score: customerScore,
account_scope: accounts,
rejected_problem_units: 0,
rejected_evidence: 0,
critical_contradiction: unresolved,
reason_codes: unresolved ? ["unresolved_supplier_customer_polarity"] : []
};
}
function applyPolarityHintToExecutionPlan(executionPlan, polarity) {
if (!polarity.applied || polarity.polarity === "mixed_or_unresolved" || polarity.polarity === "not_applicable") {
return executionPlan;
}
const hint = polarity.polarity === "supplier_payable"
? "контекст: расчеты с поставщиком, обязательство, счет 60"
: "контекст: расчеты с покупателем, дебиторская задолженность, счет 62";
return executionPlan.map((item) => {
if (!item.should_execute) {
return item;
}
const text = String(item.fragment_text ?? "").trim();
if (polarity.polarity === "supplier_payable" && /(поставщ|supplier|счет\s*60|по\s*60)/i.test(text)) {
return item;
}
if (polarity.polarity === "customer_receivable" && /(покупат|customer|счет\s*62|по\s*62)/i.test(text)) {
return item;
}
return {
...item,
fragment_text: `${text}; ${hint}`.trim()
};
});
}
function containsReceivableSignal(value) {
return /(?:customer_settlement|stale_receivable|receivable_closed|receivable|дебитор)/i.test(value);
}
function containsPayableSignal(value) {
return /(?:bank_settlement|payable|обязательств|supplier|поставщ|счет\s*60|\b60(?:\.\d{2})?\b)/i.test(value);
}
function problemUnitCorpus(unit) {
return [
unit.lifecycle_domain ?? "",
unit.problem_unit_type,
unit.business_defect_class ?? "",
unit.failed_expected_edge ?? "",
unit.invalid_transition ?? "",
unit.mechanism_summary ?? "",
unit.business_lifecycle_interpretation ?? "",
...(unit.affected_accounts ?? [])
]
.join(" ")
.toLowerCase();
}
function isProblemUnitCompatible(unit, polarity) {
if (polarity === "supplier_payable") {
return !containsReceivableSignal(problemUnitCorpus(unit));
}
if (polarity === "customer_receivable") {
return !containsPayableSignal(problemUnitCorpus(unit));
}
return true;
}
function evidenceCorpus(evidence) {
return JSON.stringify({
limitation: evidence.limitation,
payload: evidence.payload,
mechanism: evidence.mechanism_note,
source_ref: evidence.source_ref,
pointer: evidence.pointer
}).toLowerCase();
}
function evidenceAccounts(evidence) {
const payload = toObject(evidence.payload);
const direct = uniqueStrings([
...extractAccountsFromUnknown(payload?.account_context),
...extractAccountsFromUnknown(payload?.account_debit),
...extractAccountsFromUnknown(payload?.account_credit)
]);
if (direct.length > 0) {
return direct;
}
return uniqueStrings([
...extractAccountsFromUnknown(evidence.payload),
...extractAccountsFromUnknown(evidence.pointer ?? null)
]);
}
function isEvidenceCompatibleWithPolarity(evidence, polarity) {
const corpus = evidenceCorpus(evidence);
const accounts = evidenceAccounts(evidence).map((item) => accountPrefix(item)).filter((item) => Boolean(item));
if (polarity === "supplier_payable") {
if (containsReceivableSignal(corpus)) {
return false;
}
if (accounts.length > 0 && accounts.every((item) => item === "62")) {
return false;
}
}
if (polarity === "customer_receivable") {
if (containsPayableSignal(corpus)) {
return false;
}
if (accounts.length > 0 && accounts.every((item) => item === "60")) {
return false;
}
}
return true;
}
function applyDomainPolarityGuardToRetrievalResults(input) {
if (!input.guard.applied || input.guard.polarity === "not_applicable" || input.guard.polarity === "mixed_or_unresolved") {
return {
retrievalResults: input.retrievalResults,
audit: input.guard
};
}
let rejectedProblemUnits = 0;
let rejectedEvidence = 0;
let criticalContradiction = false;
const adjusted = input.retrievalResults.map((result) => {
const originalUnits = Array.isArray(result.problem_units) ? result.problem_units : [];
const filteredUnits = originalUnits.filter((unit) => isProblemUnitCompatible(unit, input.guard.polarity));
rejectedProblemUnits += Math.max(0, originalUnits.length - filteredUnits.length);
const originalEvidence = Array.isArray(result.evidence) ? result.evidence : [];
const filteredEvidence = originalEvidence.filter((item) => isEvidenceCompatibleWithPolarity(item, input.guard.polarity));
rejectedEvidence += Math.max(0, originalEvidence.length - filteredEvidence.length);
if (originalUnits.length > 0 && filteredUnits.length === 0 && originalEvidence.length > 0 && filteredEvidence.length === 0) {
criticalContradiction = true;
}
return {
...result,
evidence: filteredEvidence,
...(Array.isArray(result.problem_units)
? {
problem_units: filteredUnits
}
: {})
};
});
const reasonCodes = [];
if (rejectedProblemUnits > 0) {
reasonCodes.push("polarity_problem_unit_filter_applied");
}
if (rejectedEvidence > 0) {
reasonCodes.push("polarity_evidence_filter_applied");
}
if (criticalContradiction) {
reasonCodes.push("critical_domain_polarity_contradiction");
}
return {
retrievalResults: adjusted,
audit: {
...input.guard,
rejected_problem_units: rejectedProblemUnits,
rejected_evidence: rejectedEvidence,
critical_contradiction: criticalContradiction,
outcome: criticalContradiction ? "blocked_conflict" : "passed",
reason_codes: uniqueStrings([...(input.guard.reason_codes ?? []), ...reasonCodes])
}
};
}
function initRejectBreakdown() {
return {
wrong_period: 0,
wrong_domain: 0,
wrong_account_scope: 0,
weak_source_mapping: 0,
zero_live_match: 0,
future_dated_or_out_of_window: 0
};
}
function isVatPrefix(prefix) {
return prefix === "19" || prefix === "68";
}
function isSettlementPrefix(prefix) {
return prefix === "51" || prefix === "60" || prefix === "62" || prefix === "76";
}
function isMonthClosePrefix(prefix) {
const numeric = Number(prefix);
if (prefix === "97") {
return true;
}
if (!Number.isFinite(numeric)) {
return false;
}
return numeric >= 20 && numeric <= 44;
}
function expectedAccountPrefixes(input) {
const explicit = uniqueStrings([...(input.companyAnchors?.accounts ?? []), ...extractAccountsFromText(input.userMessage)])
.map((item) => accountPrefix(item))
.filter((item) => Boolean(item));
if (explicit.length > 0) {
return uniqueStrings(explicit);
}
if (input.focusDomainHint === "vat_document_register_book") {
return ["19", "68"];
}
if (input.focusDomainHint === "month_close_costs_20_44") {
return ["20", "25", "26", "44", "97", "01", "02", "08"];
}
if (input.focusDomainHint === "settlements_60_62") {
if (input.polarity === "supplier_payable") {
return ["60", "51", "76"];
}
if (input.polarity === "customer_receivable") {
return ["62", "51"];
}
return ["60", "62", "51", "76"];
}
return [];
}
function isLiveEvidence(evidence) {
const payload = toObject(evidence.payload);
if (String(payload?.source_layer ?? "").trim().toLowerCase() === "mcp_live_probe") {
return true;
}
const sourceEntity = String(evidence.pointer?.source?.entity ?? "").toLowerCase();
return sourceEntity.includes("mcplivemovement");
}
function extractEvidencePeriod(evidence) {
const payload = toObject(evidence.payload);
return (String(evidence.source_ref?.period ?? "").trim() ||
String(evidence.pointer?.source?.period ?? "").trim() ||
String(payload?.period ?? "").trim() ||
null);
}
function isExpectedAccountScopeMatch(accounts, expectedPrefixes) {
if (accounts.length === 0 || expectedPrefixes.length === 0) {
return true;
}
const prefixes = accounts.map((item) => accountPrefix(item)).filter((item) => Boolean(item));
if (prefixes.length === 0) {
return true;
}
return prefixes.some((prefix) => expectedPrefixes.includes(prefix));
}
function hasWrongDomainByAccounts(accounts, focusDomainHint) {
if (accounts.length === 0 || !focusDomainHint) {
return false;
}
const prefixes = accounts.map((item) => accountPrefix(item)).filter((item) => Boolean(item));
if (prefixes.length === 0) {
return false;
}
if (focusDomainHint === "settlements_60_62") {
return prefixes.every((prefix) => isVatPrefix(prefix));
}
if (focusDomainHint === "vat_document_register_book") {
return prefixes.every((prefix) => isSettlementPrefix(prefix) || isMonthClosePrefix(prefix));
}
if (focusDomainHint === "month_close_costs_20_44") {
return prefixes.every((prefix) => isSettlementPrefix(prefix) || isVatPrefix(prefix));
}
return false;
}
function extractLiveMatchedRows(result) {
const summary = toObject(result.summary);
const live = toObject(summary?.live_mcp);
const value = Number(live?.matched_rows);
return Number.isFinite(value) ? value : null;
}
function liveAccountScopeWasApplied(result) {
const summary = toObject(result.summary);
const live = toObject(summary?.live_mcp);
const accountScope = live?.account_scope;
return Array.isArray(accountScope) && accountScope.length > 0;
}
function evidenceAdmissibilityReasons(input) {
const reasons = new Set();
if (input.evidence.limitation?.reason_code === "weak_source_mapping") {
reasons.add("weak_source_mapping");
}
if (input.zeroLiveMatch && isLiveEvidence(input.evidence)) {
reasons.add("zero_live_match");
}
const period = extractEvidencePeriod(input.evidence);
if (period && input.temporal.primary_period_window) {
const normalized = normalizeEvidenceDate(period);
if (normalized && normalized > input.temporal.primary_period_window.to) {
reasons.add("future_dated_or_out_of_window");
}
else if (normalized && !isPeriodWithinWindow(normalized, input.temporal.primary_period_window)) {
reasons.add("wrong_period");
}
}
const accounts = evidenceAccounts(input.evidence);
if (!isExpectedAccountScopeMatch(accounts, input.expectedPrefixes)) {
reasons.add("wrong_account_scope");
}
if (hasWrongDomainByAccounts(accounts, input.focusDomainHint)) {
reasons.add("wrong_domain");
}
return Array.from(reasons);
}
function isLiveItem(item) {
return String(item.source_layer ?? "").trim().toLowerCase() === "mcp_live_probe";
}
function itemPeriod(item) {
const value = String(item.period ?? item.Period ?? "").trim();
return value || null;
}
function itemAccounts(item) {
const direct = uniqueStrings([
...extractAccountsFromUnknown(item.account_context),
...extractAccountsFromUnknown(item.account_debit),
...extractAccountsFromUnknown(item.account_credit)
]);
if (direct.length > 0) {
return direct;
}
return uniqueStrings([...extractAccountsFromUnknown(item)]);
}
function itemRejectReasons(input) {
const reasons = new Set();
if (input.zeroLiveMatch && isLiveItem(input.item)) {
reasons.add("zero_live_match");
}
const period = itemPeriod(input.item);
if (period && input.temporal.primary_period_window) {
const normalized = normalizeEvidenceDate(period);
if (normalized && normalized > input.temporal.primary_period_window.to) {
reasons.add("future_dated_or_out_of_window");
}
else if (normalized && !isPeriodWithinWindow(normalized, input.temporal.primary_period_window)) {
reasons.add("wrong_period");
}
}
const accounts = itemAccounts(input.item);
if (!isExpectedAccountScopeMatch(accounts, input.expectedPrefixes)) {
reasons.add("wrong_account_scope");
}
if (hasWrongDomainByAccounts(accounts, input.focusDomainHint)) {
reasons.add("wrong_domain");
}
return Array.from(reasons);
}
function addRejectReason(target, reason) {
target[reason] += 1;
}
function applyEvidenceAdmissibilityGate(input) {
const rejectBreakdown = initRejectBreakdown();
const categoryBreakdown = {
hard_evidence: 0,
supporting_signal: 0,
inadmissible_noise: 0
};
let candidateEvidenceTotal = 0;
let admissibleEvidenceCount = 0;
let rejectedEvidenceCount = 0;
let rejectedItemCount = 0;
const expectedPrefixes = expectedAccountPrefixes({
focusDomainHint: input.focusDomainHint,
polarity: input.polarity,
companyAnchors: input.companyAnchors,
userMessage: input.userMessage
});
const adjusted = input.retrievalResults.map((result) => {
const matchedRows = extractLiveMatchedRows(result);
const zeroLiveMatch = matchedRows === 0 && liveAccountScopeWasApplied(result);
const evidence = Array.isArray(result.evidence) ? result.evidence : [];
candidateEvidenceTotal += evidence.length;
const admissibleEvidence = [];
for (const item of evidence) {
const reasons = evidenceAdmissibilityReasons({
evidence: item,
temporal: input.temporal,
focusDomainHint: input.focusDomainHint,
expectedPrefixes,
zeroLiveMatch
});
if (reasons.length > 0) {
rejectedEvidenceCount += 1;
categoryBreakdown.inadmissible_noise += 1;
for (const reason of reasons) {
addRejectReason(rejectBreakdown, reason);
}
continue;
}
const limitationCode = String(item.limitation?.reason_code ?? "").trim();
if (!limitationCode && item.confidence !== "low") {
categoryBreakdown.hard_evidence += 1;
}
else {
categoryBreakdown.supporting_signal += 1;
}
admissibleEvidenceCount += 1;
admissibleEvidence.push(item);
}
const items = Array.isArray(result.items) ? result.items : [];
const admissibleItems = [];
for (const item of items) {
const reasons = itemRejectReasons({
item,
temporal: input.temporal,
focusDomainHint: input.focusDomainHint,
expectedPrefixes,
zeroLiveMatch
});
if (reasons.length > 0) {
rejectedItemCount += 1;
for (const reason of reasons) {
addRejectReason(rejectBreakdown, reason);
}
continue;
}
admissibleItems.push(item);
}
const summary = {
...(toObject(result.summary) ?? {}),
evidence_admissibility_gate: {
candidate_evidence: evidence.length,
admissible_evidence: admissibleEvidence.length,
rejected_evidence: Math.max(0, evidence.length - admissibleEvidence.length),
rejected_items: Math.max(0, items.length - admissibleItems.length)
}
};
const limitations = [...(result.limitations ?? [])];
if (zeroLiveMatch) {
limitations.push("Live probe matched_rows=0; live evidence excluded from grounded answer.");
}
if (admissibleEvidence.length === 0 && evidence.length > 0) {
limitations.push("Admissibility gate removed non-admissible evidence for current scope.");
}
const normalizedStatus = result.status === "ok" && admissibleEvidence.length === 0 && admissibleItems.length === 0
? "partial"
: result.status;
return {
...result,
status: normalizedStatus,
items: admissibleItems,
evidence: admissibleEvidence,
summary,
limitations: uniqueStrings(limitations)
};
});
const reasonCodes = [];
if (rejectedEvidenceCount > 0) {
reasonCodes.push("inadmissible_evidence_filtered");
}
if (admissibleEvidenceCount === 0) {
reasonCodes.push("no_admissible_evidence_for_grounded_answer");
}
if (rejectedItemCount > 0) {
reasonCodes.push("inadmissible_items_filtered");
}
return {
retrievalResults: adjusted,
audit: {
candidate_evidence_total: candidateEvidenceTotal,
admissible_evidence_count: admissibleEvidenceCount,
rejected_evidence_count: rejectedEvidenceCount,
rejected_item_count: rejectedItemCount,
reject_breakdown: rejectBreakdown,
category_breakdown: categoryBreakdown,
reason_codes: uniqueStrings(reasonCodes)
}
};
}
function evaluateGroundedAnswerEligibility(input) {
const temporalPassed = input.temporal.temporal_guard_outcome === "passed";
const polarityPassed = !input.polarity.applied || input.polarity.outcome === "passed" || input.polarity.outcome === "not_applicable";
const admissibleEvidenceCount = input.evidence.admissible_evidence_count;
const criticalContradiction = Boolean(input.polarity.critical_contradiction);
const eligible = temporalPassed && polarityPassed && admissibleEvidenceCount > 0 && !criticalContradiction;
const reasonCodes = [];
if (!temporalPassed) {
reasonCodes.push(`temporal_guard_${input.temporal.temporal_guard_outcome}`);
}
if (!polarityPassed) {
reasonCodes.push(`polarity_guard_${input.polarity.outcome}`);
}
if (admissibleEvidenceCount <= 0) {
reasonCodes.push("admissible_evidence_count_zero");
}
if (criticalContradiction) {
reasonCodes.push("critical_domain_or_account_contradiction");
}
return {
eligible,
temporal_passed: temporalPassed,
polarity_passed: polarityPassed,
admissible_evidence_count: admissibleEvidenceCount,
critical_contradiction: criticalContradiction,
outcome: eligible ? "grounded_allowed" : "limited_or_insufficient_evidence",
reason_codes: uniqueStrings(reasonCodes)
};
}
function applyEligibilityToGroundingCheck(groundingCheck, eligibility) {
if (eligibility.eligible) {
return groundingCheck;
}
const status = eligibility.admissible_evidence_count <= 0 || !eligibility.temporal_passed ? "no_grounded_answer" : "partial";
const reasonMap = {
admissible_evidence_count_zero: "Недостаточно допустимого evidence для обоснованного ответа.",
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.",
polarity_guard_limited_unresolved_polarity: "Не удалось надежно определить supplier/customer polarity.",
polarity_guard_blocked_conflict: "Обнаружен конфликт supplier/customer polarity в retrieval-контуре."
};
const reasons = [
...(Array.isArray(groundingCheck.reasons) ? groundingCheck.reasons : []),
...eligibility.reason_codes.map((code) => reasonMap[code] ?? code)
];
return {
...groundingCheck,
status,
reasons: uniqueStrings(reasons)
};
}
+106 -6
View File
@@ -48,6 +48,7 @@ const investigationState_1 = __importStar(require("./investigationState"));
const retrievalResultNormalizer_1 = __importStar(require("./retrievalResultNormalizer"));
const questionTypeResolver_1 = __importStar(require("./questionTypeResolver"));
const companyAnchorResolver_1 = __importStar(require("./companyAnchorResolver"));
const assistantRuntimeGuards_1 = __importStar(require("./assistantRuntimeGuards"));
function retrievalSummaryForRoute(route) {
if (route === "store_canonical")
return "Canonical accounting data path selected.";
@@ -897,6 +898,43 @@ function extractFollowupAccountAnchorsLoose(text) {
}
return Array.from(new Set(anchors));
}
function accountPrefixToken(value) {
const token = String(value ?? "").trim();
const match = token.match(/^(\d{2})/);
return match ? match[1] : null;
}
function hasCrossScopeConflictWithState(userMessage, state) {
const explicitPeriod = extractNormalizedPeriodLiteral(userMessage);
const statePeriod = compactWhitespace(state.focus.period ?? "");
if (explicitPeriod && statePeriod && explicitPeriod !== statePeriod) {
return true;
}
const inferredDomain = inferP0DomainFromMessage(userMessage);
const stateDomain = compactWhitespace(state.followup_context?.active_domain ?? state.focus.domain ?? "");
if (inferredDomain && stateDomain && inferredDomain !== stateDomain) {
return true;
}
const explicitAccounts = extractAccountTokens(userMessage);
const fallbackAccounts = explicitAccounts.length > 0 ? explicitAccounts : extractFollowupAccountAnchorsLoose(userMessage);
const knownAccounts = Array.isArray(state.focus.primary_accounts) ? state.focus.primary_accounts : [];
if (fallbackAccounts.length > 0 && knownAccounts.length > 0) {
const knownPrefixes = new Set(knownAccounts.map((item) => accountPrefixToken(item)).filter(Boolean));
const newPrefixes = new Set(fallbackAccounts.map((item) => accountPrefixToken(item)).filter(Boolean));
if (newPrefixes.size > 0 && knownPrefixes.size > 0) {
let intersects = false;
for (const prefix of newPrefixes) {
if (knownPrefixes.has(prefix)) {
intersects = true;
break;
}
}
if (!intersects) {
return true;
}
}
}
return false;
}
function inferP0DomainFromMessage(text) {
const lower = String(text ?? "").toLowerCase();
const accountTokens = extractAccountTokens(lower);
@@ -1009,9 +1047,11 @@ function buildFollowupStateBinding(input) {
Boolean(problemState) &&
((problemState?.active_problem_units.length ?? 0) > 0 || (problemState?.focus_problem_types.length ?? 0) > 0);
const strongNewAnchorDetected = hasStrongFollowupAnchors(userMessage, input.investigationState);
const scopeConflictDetected = hasCrossScopeConflictWithState(userMessage, input.investigationState);
const periodRefinementFollowup = hasPeriodLiteral(userMessage) && problemContinuityAvailable;
const shouldBind = !smallTalkSignal &&
!strongNewAnchorDetected &&
!scopeConflictDetected &&
(followupMarker || referentialPointer || periodRefinementFollowup || (!strongSignal && shortPrompt));
if (!shouldBind) {
return {
@@ -1116,7 +1156,10 @@ function buildFollowupStateBinding(input) {
problem_continuity_available: problemContinuityAvailable,
problem_continuity_applied: problemContinuityApplied,
problem_continuity_skipped_reason: problemContinuityApplied ? null : problemContinuitySkippedReason,
strong_new_anchor_detected: strongNewAnchorDetected
strong_new_anchor_detected: strongNewAnchorDetected,
scope_isolation_applied: true,
scope_carryover_allowed: !scopeConflictDetected,
scope_reset_reason: scopeConflictDetected ? "cross_scope_conflict" : null
}
}
};
@@ -1185,11 +1228,30 @@ class AssistantService {
useMock: Boolean(payload.useMock)
};
const normalized = await this.normalizerService.normalize(normalizePayload);
const companyAnchors = (0, companyAnchorResolver_1.resolveCompanyAnchors)(userMessage);
const inferredDomainByMessage = inferP0DomainFromMessage(userMessage);
const focusDomainForGuards = inferredDomainByMessage === "settlements_60_62" ||
inferredDomainByMessage === "vat_document_register_book" ||
inferredDomainByMessage === "month_close_costs_20_44"
? inferredDomainByMessage
: null;
const temporalGuard = (0, assistantRuntimeGuards_1.resolveTemporalGuard)({
userMessage,
normalized: normalized.normalized,
companyAnchors
});
const domainPolarityGuardInitial = (0, assistantRuntimeGuards_1.resolveDomainPolarityGuard)({
userMessage,
companyAnchors,
focusDomainHint: focusDomainForGuards
});
const requirementExtraction = extractRequirements(normalized.route_hint_summary, normalized.normalized, userMessage);
const executionPlan = toExecutionPlan(normalized.route_hint_summary, normalized.normalized, userMessage, requirementExtraction.byFragment);
let executionPlan = toExecutionPlan(normalized.route_hint_summary, normalized.normalized, userMessage, requirementExtraction.byFragment);
executionPlan = (0, assistantRuntimeGuards_1.applyTemporalHintToExecutionPlan)(executionPlan, temporalGuard);
executionPlan = (0, assistantRuntimeGuards_1.applyPolarityHintToExecutionPlan)(executionPlan, domainPolarityGuardInitial);
const retrievalCalls = [];
const retrievalResultsRaw = [];
const retrievalResults = [];
let retrievalResults = [];
for (const planItem of executionPlan) {
if (!planItem.should_execute) {
retrievalCalls.push({
@@ -1248,13 +1310,32 @@ class AssistantService {
retrievalResults.push((0, retrievalResultNormalizer_1.normalizeRetrievalResult)(planItem.fragment_id, planItem.requirement_ids, planItem.route, rawError));
}
}
const polarityGuardResult = (0, assistantRuntimeGuards_1.applyDomainPolarityGuardToRetrievalResults)({
retrievalResults,
guard: domainPolarityGuardInitial
});
retrievalResults = polarityGuardResult.retrievalResults;
const evidenceGateResult = (0, assistantRuntimeGuards_1.applyEvidenceAdmissibilityGate)({
retrievalResults,
temporal: temporalGuard,
focusDomainHint: focusDomainForGuards,
polarity: polarityGuardResult.audit.polarity,
companyAnchors,
userMessage
});
retrievalResults = evidenceGateResult.retrievalResults;
const coverageEvaluation = evaluateCoverage(requirementExtraction.requirements, retrievalResults);
const groundingCheck = checkGrounding(userMessage, coverageEvaluation.requirements, coverageEvaluation.coverage, retrievalResults);
const groundingCheckBase = checkGrounding(userMessage, coverageEvaluation.requirements, coverageEvaluation.coverage, retrievalResults);
const groundedAnswerEligibilityGuard = (0, assistantRuntimeGuards_1.evaluateGroundedAnswerEligibility)({
temporal: temporalGuard,
polarity: polarityGuardResult.audit,
evidence: evidenceGateResult.audit
});
const groundingCheck = (0, assistantRuntimeGuards_1.applyEligibilityToGroundingCheck)(groundingCheckBase, groundedAnswerEligibilityGuard);
const focusDomainHint = followupBinding.usage?.applied
? session.investigation_state?.followup_context?.active_domain ?? session.investigation_state?.focus.domain ?? null
: null;
const questionTypeClass = (0, questionTypeResolver_1.resolveQuestionType)(userMessage);
const companyAnchors = (0, companyAnchorResolver_1.resolveCompanyAnchors)(userMessage);
const hasPeriodInCompanyAnchors = (Array.isArray(companyAnchors?.dates) && companyAnchors.dates.some((item) => String(item ?? "").trim().length > 0)) ||
(Array.isArray(companyAnchors?.periods) && companyAnchors.periods.some((item) => String(item ?? "").trim().length > 0));
const normalizationPeriodExplicit = hasExplicitPeriodAnchorFromNormalized(normalized.normalized) || hasPeriodInCompanyAnchors;
@@ -1298,7 +1379,8 @@ class AssistantService {
requirements: coverageEvaluation.requirements,
coverageReport: coverageEvaluation.coverage,
retrievalResults,
replyType: composition.reply_type
replyType: composition.reply_type,
followupApplied: Boolean(followupBinding.usage?.applied)
})
: null;
if (config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1 && investigationStateSnapshot) {
@@ -1326,6 +1408,15 @@ class AssistantService {
dropped_intent_segments: extractDiscardedIntentSegments(normalized.normalized),
question_type_class: questionTypeClass,
company_anchors: companyAnchors,
raw_time_anchor: temporalGuard.raw_time_anchor,
resolved_time_anchor: temporalGuard.resolved_time_anchor,
temporal_resolution_source: temporalGuard.temporal_resolution_source,
temporal_guard_applied: temporalGuard.temporal_guard_applied,
temporal_guard_outcome: temporalGuard.temporal_guard_outcome,
temporal_guard: temporalGuard,
domain_polarity_guard: polarityGuardResult.audit,
evidence_admissibility_gate: evidenceGateResult.audit,
grounded_answer_eligibility_guard: groundedAnswerEligibilityGuard,
...(followupBinding.usage ? { followup_state_usage: followupBinding.usage } : {}),
problem_centric_answer_applied: composition.problem_centric_answer_applied ?? false,
problem_units_used_count: composition.problem_units_used_count ?? 0,
@@ -1391,6 +1482,15 @@ class AssistantService {
dropped_intent_segments: extractDiscardedIntentSegments(normalized.normalized),
question_type_class: questionTypeClass,
company_anchors: companyAnchors,
raw_time_anchor: temporalGuard.raw_time_anchor,
resolved_time_anchor: temporalGuard.resolved_time_anchor,
temporal_resolution_source: temporalGuard.temporal_resolution_source,
temporal_guard_applied: temporalGuard.temporal_guard_applied,
temporal_guard_outcome: temporalGuard.temporal_guard_outcome,
temporal_guard: temporalGuard,
domain_polarity_guard: polarityGuardResult.audit,
evidence_admissibility_gate: evidenceGateResult.audit,
grounded_answer_eligibility_guard: groundedAnswerEligibilityGuard,
...(followupBinding.usage ? { followup_state_usage: followupBinding.usage } : {}),
problem_centric_answer_applied: composition.problem_centric_answer_applied ?? false,
problem_units_used_count: composition.problem_units_used_count ?? 0,
+95 -8
View File
@@ -23,6 +23,63 @@ function detectPeriod(text) {
return yearly[1];
return null;
}
function detectExplicitDomainHint(text) {
const messageCorpus = String(text ?? "").toLowerCase();
const accounts = detectAccounts(text);
const hasSettlementSignal = accounts.some((item) => isSettlementAccount(item)) ||
/(?:60(?:\.\d{2})?|62(?:\.\d{2})?|оплат|расч[её]т|зач[её]т|аванс|долг|поставщ|покупат|settlement|payment|supplier|customer)/i.test(messageCorpus);
if (hasSettlementSignal) {
return "settlements_60_62";
}
const hasVatSignal = accounts.some((item) => isVatAccount(item)) ||
/(?:ндс|сч[её]т[\s-]?фактур|книг[аи]|vat|invoice|book|register)/i.test(messageCorpus);
if (hasVatSignal) {
return "vat_document_register_book";
}
const hasCloseSignal = accounts.some((item) => isCloseCostsAccount(item)) ||
/(?:закрыти|месяц|затрат|распредел|списан|period\s*close|month\s*close|allocation|residual|cost|рбп)/i.test(messageCorpus);
if (hasCloseSignal) {
return "month_close_costs_20_44";
}
const hasFixedAssetSignal = accounts.some((item) => isFixedAssetAccount(item)) ||
/(?:амортиз|основн(ые|ых|ым)?\s+средств|(?:^|[^a-zа-яё])ос(?:$|[^a-zа-яё])|объект[а-яё]*\s+ос|fixed\s*asset|depreciat)/i.test(messageCorpus);
if (hasFixedAssetSignal) {
return "fixed_asset_amortization";
}
return null;
}
function buildQuestionScopeId(input) {
const domainPart = String(input.domain ?? "").trim();
const periodPart = String(input.period ?? "").trim();
const accountPart = capStrings(input.accounts.map((item) => String(item ?? "").trim()).filter(Boolean), 4).join(",");
const subjectPart = String(input.subject ?? "").trim().slice(0, 96).toLowerCase();
const parts = [
domainPart ? `d:${domainPart}` : "",
periodPart ? `p:${periodPart}` : "",
accountPart ? `a:${accountPart}` : "",
subjectPart ? `s:${subjectPart}` : ""
].filter(Boolean);
if (parts.length === 0) {
return null;
}
return parts.join("|");
}
function deriveScopeOrigin(input) {
if (input.followupApplied) {
return "followup_state_carryover";
}
const hasExplicitPeriod = Boolean(detectPeriod(input.userMessage));
const hasExplicitAccounts = detectAccounts(input.userMessage).length > 0;
const explicitDomain = detectExplicitDomainHint(input.userMessage);
if (hasExplicitPeriod || hasExplicitAccounts || explicitDomain) {
return "explicit_from_message";
}
const routeDomain = deriveDomain(input.routeSummary);
if (routeDomain && routeDomain !== "no_route") {
return "route_derived";
}
return "underspecified";
}
function deriveDomain(routeSummary) {
if (!routeSummary)
return null;
@@ -105,7 +162,9 @@ function isCloseCostsAccount(value) {
}
function inferFollowupActiveDomain(input) {
const messageCorpus = String(input.userMessage ?? "").toLowerCase();
const contextualCorpus = `${messageCorpus} ${input.previous.focus.active_query_subject ?? ""}`.toLowerCase();
const contextualCorpus = input.allowStateCarryover
? `${messageCorpus} ${input.previous.focus.active_query_subject ?? ""}`.toLowerCase()
: messageCorpus;
const hasFixedAssetLexicalSignal = /(?:амортиз|основн(ые|ых|ым)?\s+средств|(?:^|[^a-zа-яё])ос(?:$|[^a-zа-яё])|объект[а-яё]*\s+ос|fixed\s*asset|depreciat)/i.test(messageCorpus);
const hasFixedAssetAccountSignal = input.focusAccounts.some((item) => isFixedAssetAccount(item)) &&
/(?:сч[её]т(?:а|у|ом|е)?\s*(?:01|02|08)|(?:01|02|08)(?:\.\d{2})?\s*\/\s*(?:01|02|08)(?:\.\d{2})?|\b0[128](?:\.\d{2})?\b)/i.test(messageCorpus);
@@ -127,7 +186,8 @@ function inferFollowupActiveDomain(input) {
if (hasCloseSignal) {
return "month_close_costs_20_44";
}
if (/(?:60(?:\.\d{2})?|62(?:\.\d{2})?|оплат|расч[её]т|аванс|долг|settlement|payment)/i.test(contextualCorpus) &&
if (input.allowStateCarryover &&
/(?:60(?:\.\d{2})?|62(?:\.\d{2})?|оплат|расч[её]т|аванс|долг|settlement|payment)/i.test(contextualCorpus) &&
(input.previous.followup_context?.active_domain === "settlements_60_62" ||
input.previous.focus.domain === "settlements_60_62")) {
return "settlements_60_62";
@@ -136,7 +196,10 @@ function inferFollowupActiveDomain(input) {
if (routeDomain && routeDomain !== "no_route") {
return routeDomain;
}
return input.previous.followup_context?.active_domain ?? input.previous.focus.domain ?? null;
if (input.allowStateCarryover) {
return input.previous.followup_context?.active_domain ?? input.previous.focus.domain ?? null;
}
return null;
}
function collectUncoveredRequirementIds(coverageReport) {
return capStrings([
@@ -313,6 +376,8 @@ function createEmptyInvestigationState(sessionId, timestamp = new Date().toISOSt
turn_index: 0,
updated_at: timestamp,
question_id: null,
question_scope_id: null,
scope_origin: null,
focus: {
domain: null,
period: null,
@@ -329,22 +394,40 @@ function createEmptyInvestigationState(sessionId, timestamp = new Date().toISOSt
}
function updateInvestigationState(input) {
const previous = input.previous;
const followupApplied = input.followupApplied === true;
const focusFromMessage = capStrings(detectAccounts(input.userMessage), stage1Contracts_1.INVESTIGATION_MAX_PRIMARY_ACCOUNTS);
const mergedFocusAccounts = capStrings([...focusFromMessage, ...previous.focus.primary_accounts], stage1Contracts_1.INVESTIGATION_MAX_PRIMARY_ACCOUNTS);
const mergedFocusAccounts = followupApplied
? capStrings([...focusFromMessage, ...previous.focus.primary_accounts], stage1Contracts_1.INVESTIGATION_MAX_PRIMARY_ACCOUNTS)
: capStrings(focusFromMessage, stage1Contracts_1.INVESTIGATION_MAX_PRIMARY_ACCOUNTS);
const requirementIds = capStrings(input.requirements.map((item) => item.requirement_id), stage1Contracts_1.INVESTIGATION_MAX_REQUIREMENT_LINKS);
const mainRequirement = input.requirements[0]?.requirement_text ?? input.userMessage;
const problemUnitState = updateProblemUnitState(previous, input.retrievalResults);
const uncoveredRequirementIds = collectUncoveredRequirementIds(input.coverageReport);
const routeDomain = deriveDomain(input.routeSummary);
const activeDomain = inferFollowupActiveDomain({
userMessage: input.userMessage,
focusAccounts: focusFromMessage,
routeSummary: input.routeSummary,
previous
previous,
allowStateCarryover: followupApplied
});
const focusDomain = activeDomain ?? deriveDomain(input.routeSummary) ?? previous.focus.domain;
const focusDomain = activeDomain ?? routeDomain ?? (followupApplied ? previous.focus.domain : null);
const detectedPeriod = detectPeriod(input.userMessage);
const focusPeriod = detectedPeriod ?? (followupApplied ? previous.focus.period : null);
const settlementNextActions = settlementFocusActions(activeDomain);
const lastProblemUnitId = problemUnitState?.active_problem_units[0] ?? null;
const evidenceSummary = collectEvidenceSummary(input.retrievalResults);
const scopeOrigin = deriveScopeOrigin({
followupApplied,
userMessage: input.userMessage,
routeSummary: input.routeSummary
});
const questionScopeId = buildQuestionScopeId({
domain: focusDomain,
period: focusPeriod,
accounts: mergedFocusAccounts,
subject: mainRequirement
});
return {
schema_version: stage1Contracts_1.INVESTIGATION_STATE_SCHEMA_VERSION,
session_id: previous.session_id,
@@ -352,9 +435,11 @@ function updateInvestigationState(input) {
turn_index: previous.turn_index + 1,
updated_at: input.timestamp,
question_id: input.questionId,
question_scope_id: questionScopeId,
scope_origin: scopeOrigin,
focus: {
domain: focusDomain,
period: detectPeriod(input.userMessage) ?? previous.focus.period,
period: focusPeriod,
primary_accounts: mergedFocusAccounts,
active_query_subject: mainRequirement.slice(0, 180)
},
@@ -371,7 +456,9 @@ function updateInvestigationState(input) {
uncovered_requirement_ids: uncoveredRequirementIds,
last_problem_unit_id: lastProblemUnitId,
settlement_next_actions: settlementNextActions,
evidence_summary: evidenceSummary
evidence_summary: evidenceSummary,
question_scope_id: questionScopeId,
scope_origin: scopeOrigin
},
query_mode_hint: deriveQueryModeHint(input.routeSummary),
...(problemUnitState