"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createEmptyAddressNavigationState = createEmptyAddressNavigationState; exports.cloneAddressNavigationState = cloneAddressNavigationState; exports.normalizeAddressNavigationState = normalizeAddressNavigationState; exports.evolveAddressNavigationStateWithAssistantItem = evolveAddressNavigationStateWithAssistantItem; const nanoid_1 = require("nanoid"); const addressNavigation_1 = require("../types/addressNavigation"); const MAX_RESULT_SETS = 40; const MAX_NAVIGATION_EVENTS = 120; const MAX_ENTITY_REFS_PER_RESULT_SET = 40; const DISPLAY_ENTITY_TYPE_BY_INTENT = { counterparty_activity_lifecycle: "counterparty", customer_revenue_and_payments: "counterparty", supplier_payouts_profile: "counterparty", list_payables_counterparties: "counterparty", list_receivables_counterparties: "counterparty", list_contracts_by_counterparty: "contract", list_documents_by_counterparty: "document_ref", list_documents_by_contract: "document_ref", bank_operations_by_counterparty: "document_ref", bank_operations_by_contract: "document_ref", open_items_by_counterparty_or_contract: "counterparty" }; const RESULT_SET_TYPE_BY_INTENT = { counterparty_activity_lifecycle: "counterparty_list", customer_revenue_and_payments: "counterparty_list", supplier_payouts_profile: "counterparty_list", list_payables_counterparties: "counterparty_list", payables_confirmed_as_of_date: "balance_snapshot", list_receivables_counterparties: "counterparty_list", list_contracts_by_counterparty: "contract_list", list_documents_by_counterparty: "document_list", list_documents_by_contract: "document_list", bank_operations_by_counterparty: "bank_operations_list", bank_operations_by_contract: "bank_operations_list", open_items_by_counterparty_or_contract: "open_items_list", period_coverage_profile: "profile_summary", document_type_and_account_section_profile: "profile_summary", counterparty_population_and_roles: "profile_summary", contract_usage_overview: "profile_summary", contract_usage_and_value: "profile_summary", vat_payable_forecast: "profile_summary" }; function toObject(value) { if (!value || typeof value !== "object" || Array.isArray(value)) { return null; } return value; } function toNonEmptyString(value) { if (typeof value !== "string") { return null; } const trimmed = value.trim(); return trimmed.length > 0 ? trimmed : null; } function toAddressFocusObjectType(value) { const normalized = toNonEmptyString(value); if (!normalized) { return "unknown"; } if (normalized === "counterparty" || normalized === "contract" || normalized === "document_ref" || normalized === "account") { return normalized; } return "unknown"; } function toAddressIntent(value) { const normalized = toNonEmptyString(value); return (normalized ?? "unknown"); } function inferDisplayEntityType(intent) { return DISPLAY_ENTITY_TYPE_BY_INTENT[intent] ?? "unknown"; } function inferResultSetType(intent) { return RESULT_SET_TYPE_BY_INTENT[intent] ?? "unknown"; } function parseEntityCandidateFromLine(line) { const compact = String(line ?? "").trim(); if (!compact) { return null; } const numberedMatch = compact.match(/^(\d+)\.\s+(.+)$/); if (!numberedMatch) { return null; } const index = Number.parseInt(String(numberedMatch[1] ?? ""), 10); if (!Number.isFinite(index) || index <= 0) { return null; } const afterNumber = String(numberedMatch[2] ?? ""); const pieces = afterNumber.split("|").map((item) => item.trim()).filter(Boolean); const valueCandidate = pieces.length > 0 ? pieces[0] : afterNumber; const cleaned = valueCandidate.replace(/^["'«»“”„`’‘]+|["'«»“”„`’‘]+$/gu, "").trim(); if (!cleaned || cleaned.length < 2) { return null; } return { index, value: cleaned }; } function extractEntityRefsFromAssistantReply(replyText, intent, limit = MAX_ENTITY_REFS_PER_RESULT_SET) { const entityType = inferDisplayEntityType(intent); if (entityType === "unknown") { return []; } const dedup = new Map(); const lines = String(replyText ?? "").split(/\r?\n/); for (const line of lines) { const parsed = parseEntityCandidateFromLine(line); if (!parsed) { continue; } const key = `${parsed.index}:${entityType}:${parsed.value.toLowerCase()}`; if (!dedup.has(key)) { dedup.set(key, { index: parsed.index, entity_type: entityType, value: parsed.value }); } if (dedup.size >= limit) { break; } } return Array.from(dedup.values()); } function cloneFocusObject(value) { if (!value) { return null; } return { object_type: value.object_type, object_id: value.object_id, label: value.label, provenance_result_set_id: value.provenance_result_set_id, selected_at: value.selected_at }; } function cloneResultSet(input) { return { result_set_id: input.result_set_id, type: input.type, intent: input.intent, route_id: input.route_id, filters: { ...input.filters }, source_refs: [...input.source_refs], entity_refs: input.entity_refs.map((item) => ({ index: item.index, entity_type: item.entity_type, value: item.value })), created_from_turn: input.created_from_turn, created_at: input.created_at }; } function cloneNavigationEvent(input) { return { event_id: input.event_id, action: input.action, source_result_set_id: input.source_result_set_id, target_object_id: input.target_object_id, derived_result_set_id: input.derived_result_set_id, turn_index: input.turn_index, created_at: input.created_at }; } function normalizeFilters(value) { const record = toObject(value); if (!record) { return {}; } return { ...record }; } function resolveNavigationAction(debug, hasFocusObject) { const continuationContract = toObject(debug.dialog_continuation_contract_v2); const decision = toNonEmptyString(continuationContract?.decision); if (decision === "new_topic") { return "open"; } if (decision === "continue_previous") { return hasFocusObject ? "drilldown" : "refine"; } if (decision === "switch_to_suggested") { return "refine"; } return hasFocusObject ? "drilldown" : "open"; } function buildFocusObjectFromDebug(debug, resultSetId, createdAt) { const rawValue = toNonEmptyString(debug.anchor_value_resolved) ?? toNonEmptyString(debug.anchor_value_raw); if (!rawValue) { return null; } const objectType = toAddressFocusObjectType(debug.anchor_type); const canonicalType = objectType === "unknown" ? inferDisplayEntityType(toAddressIntent(debug.detected_intent)) : objectType; return { object_type: canonicalType, object_id: `${canonicalType}:${rawValue}`.toLowerCase(), label: rawValue, provenance_result_set_id: resultSetId, selected_at: createdAt }; } function capResultSets(resultSets) { if (resultSets.length <= MAX_RESULT_SETS) { return resultSets; } return resultSets.slice(resultSets.length - MAX_RESULT_SETS); } function capNavigationEvents(events) { if (events.length <= MAX_NAVIGATION_EVENTS) { return events; } return events.slice(events.length - MAX_NAVIGATION_EVENTS); } function isAddressAssistantItem(item) { return (item.role === "assistant" && Boolean(item.debug) && toNonEmptyString(item.debug?.detected_mode) === "address_query"); } function createEmptyAddressNavigationState(sessionId, nowIso = new Date().toISOString()) { return { schema_version: addressNavigation_1.ADDRESS_NAVIGATION_STATE_SCHEMA_VERSION, session_id: sessionId, updated_at: nowIso, session_context: { active_result_set_id: null, active_focus_object: null, last_confirmed_route: null, date_scope: { as_of_date: null, period_from: null, period_to: null }, organization_scope: null }, result_sets: [], navigation_history: [] }; } function cloneAddressNavigationState(value) { if (!value) { return null; } return { schema_version: value.schema_version, session_id: value.session_id, updated_at: value.updated_at, session_context: { active_result_set_id: value.session_context.active_result_set_id, active_focus_object: cloneFocusObject(value.session_context.active_focus_object), last_confirmed_route: value.session_context.last_confirmed_route, date_scope: { as_of_date: value.session_context.date_scope.as_of_date, period_from: value.session_context.date_scope.period_from, period_to: value.session_context.date_scope.period_to }, organization_scope: value.session_context.organization_scope }, result_sets: value.result_sets.map(cloneResultSet), navigation_history: value.navigation_history.map(cloneNavigationEvent) }; } function normalizeAddressNavigationState(value, sessionId) { const fallback = createEmptyAddressNavigationState(sessionId); if (!value || typeof value !== "object") { return fallback; } const normalizedSessionId = toNonEmptyString(value.session_id) ?? sessionId; const normalizedUpdatedAt = toNonEmptyString(value.updated_at) ?? new Date().toISOString(); const context = toObject(value.session_context) ?? {}; const dateScope = toObject(context.date_scope) ?? {}; const resultSets = Array.isArray(value.result_sets) ? value.result_sets : []; const navigationHistory = Array.isArray(value.navigation_history) ? value.navigation_history : []; return { schema_version: addressNavigation_1.ADDRESS_NAVIGATION_STATE_SCHEMA_VERSION, session_id: normalizedSessionId, updated_at: normalizedUpdatedAt, session_context: { active_result_set_id: toNonEmptyString(context.active_result_set_id), active_focus_object: cloneFocusObject(context.active_focus_object), last_confirmed_route: toNonEmptyString(context.last_confirmed_route), date_scope: { as_of_date: toNonEmptyString(dateScope.as_of_date), period_from: toNonEmptyString(dateScope.period_from), period_to: toNonEmptyString(dateScope.period_to) }, organization_scope: toNonEmptyString(context.organization_scope) }, result_sets: resultSets .map((item) => toObject(item)) .filter((item) => item !== null) .map((item) => ({ result_set_id: toNonEmptyString(item.result_set_id) ?? `rs-${(0, nanoid_1.nanoid)(10)}`, type: inferResultSetType(toAddressIntent(item.intent)), intent: toAddressIntent(item.intent), route_id: toNonEmptyString(item.route_id), filters: normalizeFilters(item.filters), source_refs: Array.isArray(item.source_refs) ? item.source_refs.map((v) => toNonEmptyString(v)).filter((v) => Boolean(v)) : [], entity_refs: Array.isArray(item.entity_refs) ? item.entity_refs .map((entry) => toObject(entry)) .filter((entry) => entry !== null) .map((entry) => ({ index: Number.isFinite(Number(entry.index)) ? Number(entry.index) : 0, entity_type: toAddressFocusObjectType(entry.entity_type), value: toNonEmptyString(entry.value) ?? "" })) .filter((entry) => entry.index > 0 && entry.value.length > 0) : [], created_from_turn: Number.isFinite(Number(item.created_from_turn)) ? Number(item.created_from_turn) : 0, created_at: toNonEmptyString(item.created_at) ?? normalizedUpdatedAt })), navigation_history: navigationHistory .map((item) => toObject(item)) .filter((item) => item !== null) .map((item) => ({ event_id: toNonEmptyString(item.event_id) ?? `nav-${(0, nanoid_1.nanoid)(10)}`, action: toNonEmptyString(item.action) ?? "open", source_result_set_id: toNonEmptyString(item.source_result_set_id), target_object_id: toNonEmptyString(item.target_object_id), derived_result_set_id: toNonEmptyString(item.derived_result_set_id), turn_index: Number.isFinite(Number(item.turn_index)) ? Number(item.turn_index) : 0, created_at: toNonEmptyString(item.created_at) ?? normalizedUpdatedAt })) }; } function evolveAddressNavigationStateWithAssistantItem(state, item, turnIndex) { if (!isAddressAssistantItem(item) || !item.debug) { return state; } const debug = item.debug; const intent = toAddressIntent(debug.detected_intent); if (intent === "unknown") { return state; } const createdAt = toNonEmptyString(item.created_at) ?? new Date().toISOString(); const resultSetId = `rs-${item.message_id}`; const routeId = toNonEmptyString(debug.selected_recipe); const filters = normalizeFilters(debug.extracted_filters); const sourceRefs = routeId ? [routeId] : []; const entityRefs = extractEntityRefsFromAssistantReply(item.text, intent); const resultSet = { result_set_id: resultSetId, type: inferResultSetType(intent), intent, route_id: routeId, filters, source_refs: sourceRefs, entity_refs: entityRefs, created_from_turn: turnIndex, created_at: createdAt }; const previousResultSetId = state.session_context.active_result_set_id; const focusObject = buildFocusObjectFromDebug(debug, resultSetId, createdAt); const action = resolveNavigationAction(debug, Boolean(focusObject)); const navigationEvent = { event_id: `nav-${(0, nanoid_1.nanoid)(10)}`, action, source_result_set_id: previousResultSetId, target_object_id: focusObject?.object_id ?? null, derived_result_set_id: resultSetId, turn_index: turnIndex, created_at: createdAt }; const normalizedDateScope = { as_of_date: toNonEmptyString(filters.as_of_date), period_from: toNonEmptyString(filters.period_from), period_to: toNonEmptyString(filters.period_to) }; const organizationScope = toNonEmptyString(filters.organization); const nextResultSets = capResultSets([...state.result_sets.filter((itemSet) => itemSet.result_set_id !== resultSetId), resultSet].sort((left, right) => left.created_from_turn - right.created_from_turn)); const nextEvents = capNavigationEvents([...state.navigation_history, navigationEvent]); return { ...state, updated_at: createdAt, session_context: { active_result_set_id: resultSetId, active_focus_object: focusObject ?? state.session_context.active_focus_object, last_confirmed_route: routeId ?? state.session_context.last_confirmed_route, date_scope: { as_of_date: normalizedDateScope.as_of_date ?? state.session_context.date_scope.as_of_date, period_from: normalizedDateScope.period_from ?? state.session_context.date_scope.period_from, period_to: normalizedDateScope.period_to ?? state.session_context.date_scope.period_to }, organization_scope: organizationScope ?? state.session_context.organization_scope }, result_sets: nextResultSets, navigation_history: nextEvents }; }