Этап 4 / Волна 18 закрытие блокеров по времени, доменной полярности и допуску доказательной базы
This commit is contained in:
@@ -7,6 +7,7 @@ import type { RouteHintSummary } from "../types/normalizer";
|
||||
import type {
|
||||
InvestigationLastAnswerMode,
|
||||
InvestigationNarrowingStatus,
|
||||
InvestigationScopeOrigin,
|
||||
InvestigationState
|
||||
} from "../types/stage1Contracts";
|
||||
import {
|
||||
@@ -39,6 +40,7 @@ interface UpdateInvestigationStateInput {
|
||||
coverageReport: RequirementCoverageReport;
|
||||
retrievalResults: UnifiedRetrievalResult[];
|
||||
replyType: InvestigationLastAnswerMode;
|
||||
followupApplied?: boolean;
|
||||
}
|
||||
|
||||
function uniqueStrings(values: string[]): string[] {
|
||||
@@ -61,6 +63,83 @@ function detectPeriod(text: string): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
function detectExplicitDomainHint(text: string): string | null {
|
||||
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: {
|
||||
domain: string | null;
|
||||
period: string | null;
|
||||
accounts: string[];
|
||||
subject: string;
|
||||
}): string | null {
|
||||
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: {
|
||||
followupApplied: boolean;
|
||||
userMessage: string;
|
||||
routeSummary: RouteHintSummary | null;
|
||||
}): InvestigationScopeOrigin {
|
||||
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: RouteHintSummary | null): string | null {
|
||||
if (!routeSummary) return null;
|
||||
if (routeSummary.mode === "legacy_v1") {
|
||||
@@ -165,9 +244,12 @@ function inferFollowupActiveDomain(input: {
|
||||
focusAccounts: string[];
|
||||
routeSummary: RouteHintSummary | null;
|
||||
previous: InvestigationStateWithProblemUnits;
|
||||
allowStateCarryover: boolean;
|
||||
}): string | null {
|
||||
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(
|
||||
@@ -206,6 +288,7 @@ function inferFollowupActiveDomain(input: {
|
||||
}
|
||||
|
||||
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")
|
||||
@@ -218,7 +301,11 @@ function inferFollowupActiveDomain(input: {
|
||||
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: RequirementCoverageReport): string[] {
|
||||
@@ -447,6 +534,8 @@ export function createEmptyInvestigationState(
|
||||
turn_index: 0,
|
||||
updated_at: timestamp,
|
||||
question_id: null,
|
||||
question_scope_id: null,
|
||||
scope_origin: null,
|
||||
focus: {
|
||||
domain: null,
|
||||
period: null,
|
||||
@@ -464,11 +553,11 @@ export function createEmptyInvestigationState(
|
||||
|
||||
export function updateInvestigationState(input: UpdateInvestigationStateInput): InvestigationStateWithProblemUnits {
|
||||
const previous = input.previous;
|
||||
const followupApplied = input.followupApplied === true;
|
||||
const focusFromMessage = capStrings(detectAccounts(input.userMessage), INVESTIGATION_MAX_PRIMARY_ACCOUNTS);
|
||||
const mergedFocusAccounts = capStrings(
|
||||
[...focusFromMessage, ...previous.focus.primary_accounts],
|
||||
INVESTIGATION_MAX_PRIMARY_ACCOUNTS
|
||||
);
|
||||
const mergedFocusAccounts = followupApplied
|
||||
? capStrings([...focusFromMessage, ...previous.focus.primary_accounts], INVESTIGATION_MAX_PRIMARY_ACCOUNTS)
|
||||
: capStrings(focusFromMessage, INVESTIGATION_MAX_PRIMARY_ACCOUNTS);
|
||||
const requirementIds = capStrings(
|
||||
input.requirements.map((item) => item.requirement_id),
|
||||
INVESTIGATION_MAX_REQUIREMENT_LINKS
|
||||
@@ -476,16 +565,31 @@ export function updateInvestigationState(input: UpdateInvestigationStateInput):
|
||||
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: INVESTIGATION_STATE_SCHEMA_VERSION,
|
||||
@@ -494,9 +598,11 @@ export function updateInvestigationState(input: UpdateInvestigationStateInput):
|
||||
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)
|
||||
},
|
||||
@@ -516,7 +622,9 @@ export function updateInvestigationState(input: UpdateInvestigationStateInput):
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user