АРЧ - Ассистент: отделить meta-followup по прошлому ответу от повторного запуска address lane
This commit is contained in:
@@ -6,6 +6,7 @@ const addressQueryClassifier_1 = require("../addressQueryClassifier");
|
||||
const addressQueryShapeClassifier_1 = require("../addressQueryShapeClassifier");
|
||||
const addressIntentResolver_1 = require("../addressIntentResolver");
|
||||
const addressFilterExtractor_1 = require("../addressFilterExtractor");
|
||||
const semanticHintOverlay_1 = require("./semanticHintOverlay");
|
||||
function hasExplicitPeriodWindow(filters) {
|
||||
return ((typeof filters.period_from === "string" && filters.period_from.trim().length > 0) ||
|
||||
(typeof filters.period_to === "string" && filters.period_to.trim().length > 0));
|
||||
@@ -253,6 +254,144 @@ function isInventoryIntent(intent) {
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date");
|
||||
}
|
||||
function isInventoryRootFrameIntent(intent) {
|
||||
return intent === "inventory_on_hand_as_of_date";
|
||||
}
|
||||
function isInventoryDrilldownFrameIntent(intent) {
|
||||
return (intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date");
|
||||
}
|
||||
function buildInventoryRootFollowupContext(followupContext) {
|
||||
if (!followupContext || !followupContext.root_intent || !followupContext.root_filters) {
|
||||
return followupContext;
|
||||
}
|
||||
return {
|
||||
...followupContext,
|
||||
previous_intent: followupContext.root_intent,
|
||||
previous_filters: { ...followupContext.root_filters },
|
||||
previous_anchor_type: followupContext.root_anchor_type ?? followupContext.previous_anchor_type,
|
||||
previous_anchor_value: followupContext.root_anchor_value ?? followupContext.previous_anchor_value,
|
||||
current_frame_kind: "inventory_root"
|
||||
};
|
||||
}
|
||||
function getTokenCount(text) {
|
||||
return String(text ?? "")
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.filter(Boolean).length;
|
||||
}
|
||||
function resolveMonthNumberFromText(text) {
|
||||
const normalized = String(text ?? "").toLowerCase();
|
||||
if (!normalized) {
|
||||
return null;
|
||||
}
|
||||
if (/январ|january|jan/iu.test(normalized))
|
||||
return 1;
|
||||
if (/феврал|february|feb/iu.test(normalized))
|
||||
return 2;
|
||||
if (/март|march|mar/iu.test(normalized))
|
||||
return 3;
|
||||
if (/апрел|april|apr/iu.test(normalized))
|
||||
return 4;
|
||||
if (/(?:^|[\s,.;:!?()\-])ма(?:й|е|я)(?=$|[\s,.;:!?()\-])|may/iu.test(normalized))
|
||||
return 5;
|
||||
if (/июн|june|jun/iu.test(normalized))
|
||||
return 6;
|
||||
if (/июл|july|jul/iu.test(normalized))
|
||||
return 7;
|
||||
if (/август|august|aug/iu.test(normalized))
|
||||
return 8;
|
||||
if (/сентябр|september|sep/iu.test(normalized))
|
||||
return 9;
|
||||
if (/октябр|october|oct/iu.test(normalized))
|
||||
return 10;
|
||||
if (/ноябр|november|nov/iu.test(normalized))
|
||||
return 11;
|
||||
if (/декабр|december|dec/iu.test(normalized))
|
||||
return 12;
|
||||
return null;
|
||||
}
|
||||
function resolveYearFromFilters(filters) {
|
||||
const candidates = [
|
||||
toNonEmptyString(filters?.as_of_date),
|
||||
toNonEmptyString(filters?.period_to),
|
||||
toNonEmptyString(filters?.period_from)
|
||||
];
|
||||
for (const candidate of candidates) {
|
||||
const match = candidate?.match(/\b((?:19|20)\d{2})\b/u);
|
||||
if (match) {
|
||||
const year = Number(match[1]);
|
||||
if (Number.isFinite(year)) {
|
||||
return year;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function hasRelativeYearHint(text) {
|
||||
return /(?:эт(?:от|ого)(?:\s+же)?\s+год|этого\s+же\s+года|того\s+же\s+года|this\s+year|same\s+year|that\s+year)/iu.test(String(text ?? ""));
|
||||
}
|
||||
function resolveRelativeMonthPeriodFromInventoryRoot(userMessage, followupContext) {
|
||||
if (!followupContext || !isInventoryRootFrameIntent(followupContext.root_intent)) {
|
||||
return null;
|
||||
}
|
||||
const month = resolveMonthNumberFromText(userMessage);
|
||||
if (!month) {
|
||||
return null;
|
||||
}
|
||||
const normalized = String(userMessage ?? "");
|
||||
if (hasExplicitPeriodLiteral(normalized) || hasExplicitCurrentDateHint(normalized)) {
|
||||
return null;
|
||||
}
|
||||
const shortTemporalPatch = getTokenCount(normalized) <= 8 || hasRelativeYearHint(normalized);
|
||||
if (!shortTemporalPatch) {
|
||||
return null;
|
||||
}
|
||||
const year = resolveYearFromFilters(followupContext.root_filters);
|
||||
if (!year) {
|
||||
return null;
|
||||
}
|
||||
const lastDay = new Date(Date.UTC(year, month, 0)).getUTCDate();
|
||||
const periodFrom = `${year}-${String(month).padStart(2, "0")}-01`;
|
||||
const periodTo = `${year}-${String(month).padStart(2, "0")}-${String(lastDay).padStart(2, "0")}`;
|
||||
return {
|
||||
period_from: periodFrom,
|
||||
period_to: periodTo,
|
||||
as_of_date: periodTo
|
||||
};
|
||||
}
|
||||
function shouldRestoreInventoryRootFrame(userMessage, intent, extractedFilters, followupContext) {
|
||||
if (!followupContext || !isInventoryRootFrameIntent(followupContext.root_intent)) {
|
||||
return false;
|
||||
}
|
||||
const currentFrameKind = followupContext.current_frame_kind ?? null;
|
||||
const previousIntent = followupContext.previous_intent;
|
||||
const comingFromInventoryDrilldown = currentFrameKind === "inventory_drilldown" || isInventoryDrilldownFrameIntent(previousIntent);
|
||||
if (!comingFromInventoryDrilldown) {
|
||||
return false;
|
||||
}
|
||||
const normalized = String(userMessage ?? "");
|
||||
if (hasSelectedObjectInventorySignal(normalized) ||
|
||||
hasInventorySupplierFollowupCue(normalized) ||
|
||||
hasInventoryPurchaseDocumentsFollowupCue(normalized) ||
|
||||
hasInventoryPurchaseDateFollowupCue(normalized) ||
|
||||
hasBareInventoryPurchaseDateFollowupCue(normalized) ||
|
||||
hasInventorySaleFollowupCue(normalized) ||
|
||||
hasInventoryPurchaseToSaleChainFollowupCue(normalized)) {
|
||||
return false;
|
||||
}
|
||||
if (intent === "inventory_on_hand_as_of_date") {
|
||||
return true;
|
||||
}
|
||||
const hasTemporalPatch = hasExplicitPeriodWindow(extractedFilters) ||
|
||||
Boolean(toNonEmptyString(extractedFilters.as_of_date)) ||
|
||||
hasExplicitPeriodLiteral(normalized) ||
|
||||
Boolean(resolveRelativeMonthPeriodFromInventoryRoot(normalized, followupContext));
|
||||
return hasTemporalPatch;
|
||||
}
|
||||
function hasSelectedObjectInventorySignal(text) {
|
||||
return /(?:по\s+выбранному\s+объекту|for\s+selected\s+object)/iu.test(String(text ?? ""));
|
||||
}
|
||||
@@ -350,6 +489,7 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
|
||||
const previousAsOfDate = toNonEmptyString(previous.as_of_date);
|
||||
const previousPeriodFrom = toNonEmptyString(previous.period_from);
|
||||
const previousPeriodTo = toNonEmptyString(previous.period_to);
|
||||
const relativeMonthFromInventoryRoot = resolveRelativeMonthPeriodFromInventoryRoot(userMessage, followupContext);
|
||||
const allTimeRequested = hasAllTimeHint(userMessage);
|
||||
const sameDateRequested = hasSameDateHint(userMessage);
|
||||
if (!toNonEmptyString(merged.organization) && previousOrganization) {
|
||||
@@ -516,6 +656,13 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
|
||||
reasons.push("as_of_date_from_open_items_followup_context");
|
||||
}
|
||||
}
|
||||
if (relativeMonthFromInventoryRoot &&
|
||||
(intent === "inventory_on_hand_as_of_date" || intent === "inventory_supplier_stock_overlap_as_of_date")) {
|
||||
merged.period_from = relativeMonthFromInventoryRoot.period_from;
|
||||
merged.period_to = relativeMonthFromInventoryRoot.period_to;
|
||||
merged.as_of_date = relativeMonthFromInventoryRoot.as_of_date;
|
||||
reasons.push("period_derived_from_inventory_root_frame_year");
|
||||
}
|
||||
if (intent === "inventory_aging_by_purchase_date") {
|
||||
const explicitItemMention = /(?:^|[\s,.;:!?()\-\u2014])(?:товар(?:у|а|ом)?|позици(?:и|я|ю)|item|row|line)(?=$|[\s,.;:!?()\-\u2014])/iu.test(String(userMessage ?? ""));
|
||||
if (toNonEmptyString(merged.item) && !explicitItemMention) {
|
||||
@@ -822,7 +969,7 @@ function deriveIntentWithFollowupContext(detectedIntent, userMessage, followupCo
|
||||
reasons: [...detectedIntent.reasons, "intent_from_followup_context"]
|
||||
};
|
||||
}
|
||||
function runAddressDecomposeStage(userMessage, followupContext) {
|
||||
function runAddressDecomposeStage(userMessage, followupContext, llmSemanticHints = null) {
|
||||
const detectedMode = (0, addressQueryClassifier_1.detectAddressQuestionMode)(userMessage);
|
||||
const shape = (0, addressQueryShapeClassifier_1.classifyAddressQueryShape)(userMessage);
|
||||
const allowExplainAsFollowup = shape.shape === "EXPLAIN_OR_REASON" &&
|
||||
@@ -850,17 +997,29 @@ function runAddressDecomposeStage(userMessage, followupContext) {
|
||||
if (mode.mode !== "address_query") {
|
||||
return null;
|
||||
}
|
||||
const intent = deriveIntentWithFollowupContext(detectedIntent, userMessage, followupContext);
|
||||
const extractedFilters = (0, addressFilterExtractor_1.extractAddressFilters)(userMessage, intent.intent);
|
||||
const followupMerged = mergeFollowupFilters(extractedFilters.extracted_filters, intent.intent, userMessage, followupContext);
|
||||
let effectiveFollowupContext = followupContext;
|
||||
let intent = deriveIntentWithFollowupContext(detectedIntent, userMessage, effectiveFollowupContext);
|
||||
let extractedFilters = (0, semanticHintOverlay_1.applyAddressLlmSemanticHintsToExtraction)((0, addressFilterExtractor_1.extractAddressFilters)(userMessage, intent.intent), llmSemanticHints);
|
||||
if (shouldRestoreInventoryRootFrame(userMessage, intent.intent, extractedFilters.extracted_filters, effectiveFollowupContext)) {
|
||||
effectiveFollowupContext = buildInventoryRootFollowupContext(effectiveFollowupContext);
|
||||
intent = {
|
||||
intent: effectiveFollowupContext?.root_intent ?? "inventory_on_hand_as_of_date",
|
||||
confidence: "low",
|
||||
reasons: [...intent.reasons, "intent_restored_to_inventory_root_frame"]
|
||||
};
|
||||
extractedFilters = (0, semanticHintOverlay_1.applyAddressLlmSemanticHintsToExtraction)((0, addressFilterExtractor_1.extractAddressFilters)(userMessage, intent.intent), llmSemanticHints);
|
||||
}
|
||||
const followupMerged = mergeFollowupFilters(extractedFilters.extracted_filters, intent.intent, userMessage, effectiveFollowupContext);
|
||||
const filters = {
|
||||
extracted_filters: followupMerged.filters,
|
||||
missing_required_filters: resolveMissingRequiredFilters(intent.intent, followupMerged.filters),
|
||||
warnings: [...new Set([...extractedFilters.warnings, ...followupMerged.reasons])]
|
||||
warnings: [...new Set([...extractedFilters.warnings, ...followupMerged.reasons])],
|
||||
semantic_frame: extractedFilters.semantic_frame
|
||||
};
|
||||
const followupContextApplied = Boolean(followupContext) &&
|
||||
const followupContextApplied = Boolean(effectiveFollowupContext) &&
|
||||
(mode.reasons.includes("address_mode_from_followup_context") ||
|
||||
intent.reasons.includes("intent_from_followup_context") ||
|
||||
intent.reasons.includes("intent_restored_to_inventory_root_frame") ||
|
||||
followupMerged.reasons.length > 0);
|
||||
const baseReasons = [
|
||||
...mode.reasons,
|
||||
|
||||
+16
-4
@@ -6,10 +6,11 @@ const addressQueryClassifier_1 = require("../addressQueryClassifier");
|
||||
const addressQueryShapeClassifier_1 = require("../addressQueryShapeClassifier");
|
||||
const addressIntentResolver_1 = require("../addressIntentResolver");
|
||||
const addressFilterExtractor_1 = require("../addressFilterExtractor");
|
||||
const ADDRESS_SEMANTIC_DATA_SIGNAL_PATTERN = /(?:\u0434\u043e\u043a|\u0434\u043e\u0433\u043e\u0432\u043e\u0440|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442|\u0441\u0447(?:\u0435|\u0451)\u0442|\u0441\u0430\u043b\u044c\u0434\u043e|\u043e\u0431\u043e\u0440\u043e\u0442|\u043f\u043b\u0430\u0442(?:\u0435|\u0451)\u0436|\u043e\u043f\u0435\u0440\u0430\u0446|\u043f\u0435\u0440\u0438\u043e\u0434|\u0433\u043e\u0434|counterparty|contract|document|account|balance|turnover|operations?|doki|doky|dokument|dogovor|kontragent|schet|saldo|platezh|oplata)/iu;
|
||||
const semanticHintOverlay_1 = require("./semanticHintOverlay");
|
||||
const ADDRESS_SEMANTIC_DATA_SIGNAL_PATTERN = /(?:\u0434\u043e\u043a|\u0434\u043e\u0433\u043e\u0432\u043e\u0440|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442|\u0441\u0447(?:\u0435|\u0451)\u0442|\u0441\u0430\u043b\u044c\u0434\u043e|\u043e\u0431\u043e\u0440\u043e\u0442|\u043f\u043b\u0430\u0442(?:\u0435|\u0451)\u0436|\u043e\u043f\u0435\u0440\u0430\u0446|\u043f\u0435\u0440\u0438\u043e\u0434|\u0433\u043e\u0434|\u0441\u043a\u043b\u0430\u0434|\u0442\u043e\u0432\u0430\u0440|\u043d\u043e\u043c\u0435\u043d\u043a\u043b\u0430\u0442\u0443\u0440|counterparty|contract|document|account|balance|turnover|operations?|warehouse|stock|inventory|item|goods|doki|doky|dokument|dogovor|kontragent|schet|saldo|platezh|oplata)/iu;
|
||||
const ADDRESS_SEMANTIC_ENTITY_SIGNAL_PATTERN = /(?:\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u043a\u043e\u043d\u0442\u043e\u0440|customer|supplier|counterparty|company|vendor|client)/iu;
|
||||
const ADDRESS_SEMANTIC_SCOPE_META_PATTERN = /(?:\u043a\u0430\u043a\u0430\u044f\s+\u0431\u0430\u0437\u0430|\u0431\u0430\u0437\u0430\s+\u043a\u0430\u043a\u043e\u0439\s+\u043a\u043e\u043d\u0442\u043e\u0440|\u043f\u043e\s+\u043a\u0430\u043a\u0438\u043c\s+\u043a\u043e\u043d\u0442\u043e\u0440|which\s+company\s+base|which\s+tenant|data\s+scope)/iu;
|
||||
const ADDRESS_SEMANTIC_DEEP_INVESTIGATION_PATTERN = /(?:\u043f\u0440\u043e\u0432\u0435\u0440(?:\u044c|\u0438\u0442\u044c)|\u0440\u0430\u0437\u0431\u0435\u0440(?:\u0438|\u0430\u0442\u044c)|\u043f\u043e\u0447\u0435\u043c\u0443|\u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c|\u0440\u0430\u0437\u0440\u044b\u0432|\u0445\u0432\u043e\u0441\u0442|root\s*cause|trace\s*chain|state\s+transition)/iu;
|
||||
const ADDRESS_SEMANTIC_DEEP_INVESTIGATION_PATTERN = /(?:\u043f\u043e\u0447\u0435\u043c\u0443|\u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c|\u0440\u0430\u0437\u0440\u044b\u0432|\u0445\u0432\u043e\u0441\u0442|root\s*cause|trace\s*chain|state\s+transition|\u043f\u0440\u043e\u0432\u0435\u0440(?:\u044c|\u0438\u0442\u044c).*(?:\u0445\u0432\u043e\u0441\u0442|\u0440\u0430\u0437\u0440\u044b\u0432|\u0437\u0430\u043a\u0440\u044b\u0442|\u0446\u0435\u043f\u043e\u0447|\u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c|\u043e\u0448\u0438\u0431|\u0430\u043d\u043e\u043c\u0430\u043b|\u0440\u0438\u0441\u043a|\u0441\u0432\u0435\u0440\u043a)|\u0440\u0430\u0437\u0431\u0435\u0440(?:\u0438|\u0430\u0442\u044c).*(?:\u043f\u043e\u0447\u0435\u043c\u0443|\u0445\u0432\u043e\u0441\u0442|\u0440\u0430\u0437\u0440\u044b\u0432|\u0437\u0430\u043a\u0440\u044b\u0442|\u0446\u0435\u043f\u043e\u0447|\u043e\u0448\u0438\u0431|\u0430\u043d\u043e\u043c\u0430\u043b|\u0440\u0438\u0441\u043a))/iu;
|
||||
function normalizeCompact(value) {
|
||||
return String(value ?? "")
|
||||
.toLowerCase()
|
||||
@@ -127,8 +128,17 @@ function buildAddressLlmPredecomposeContractV1(input) {
|
||||
const mode = (0, addressQueryClassifier_1.detectAddressQuestionMode)(canonicalMessage);
|
||||
const shape = (0, addressQueryShapeClassifier_1.classifyAddressQueryShape)(canonicalMessage);
|
||||
const intent = (0, addressIntentResolver_1.resolveAddressIntent)(canonicalMessage);
|
||||
const extraction = (0, addressFilterExtractor_1.extractAddressFilters)(canonicalMessage, intent.intent);
|
||||
const extraction = (0, semanticHintOverlay_1.applyAddressLlmSemanticHintsToExtraction)((0, addressFilterExtractor_1.extractAddressFilters)(canonicalMessage, intent.intent), input.semanticHints ?? null);
|
||||
const filters = extraction.extracted_filters;
|
||||
const semanticFrame = extraction.semantic_frame ?? {
|
||||
scope_kind: "none",
|
||||
anchor_kind: "none",
|
||||
anchor_value: null,
|
||||
date_scope_kind: "none",
|
||||
date_basis_hint: null,
|
||||
self_scope_detected: false,
|
||||
selected_object_scope_detected: false
|
||||
};
|
||||
const periodScope = inferPeriodScope(filters, canonicalMessage);
|
||||
return {
|
||||
schema_version: "address_llm_predecompose_contract_v1",
|
||||
@@ -153,8 +163,9 @@ function buildAddressLlmPredecomposeContractV1(input) {
|
||||
period_from: toNonEmptyString(filters.period_from),
|
||||
period_to: toNonEmptyString(filters.period_to),
|
||||
as_of_date: toNonEmptyString(filters.as_of_date),
|
||||
has_explicit_period: Boolean(toNonEmptyString(filters.as_of_date) || toNonEmptyString(filters.period_from) || toNonEmptyString(filters.period_to))
|
||||
has_explicit_period: semanticFrame.date_scope_kind === "explicit"
|
||||
},
|
||||
semantics: semanticFrame,
|
||||
aggregation_profile: inferAggregationProfile(intent.intent, shape.shape)
|
||||
};
|
||||
}
|
||||
@@ -238,6 +249,7 @@ function buildAddressSemanticExtractionContractV1(input) {
|
||||
as_of_date: predecomposeContract.period.as_of_date,
|
||||
has_explicit_period: predecomposeContract.period.has_explicit_period
|
||||
},
|
||||
semantics: predecomposeContract.semantics,
|
||||
guard_hints: {
|
||||
source_data_signal_detected: sourceDataSignal,
|
||||
canonical_data_signal_detected: canonicalDataSignal,
|
||||
|
||||
@@ -143,6 +143,7 @@ function resolvePrimaryAnchor(intent, filters) {
|
||||
const contract = typeof filters.contract === "string" ? filters.contract.trim() : "";
|
||||
const item = typeof filters.item === "string" ? filters.item.trim() : "";
|
||||
const warehouse = typeof filters.warehouse === "string" ? filters.warehouse.trim() : "";
|
||||
const organization = typeof filters.organization === "string" ? filters.organization.trim() : "";
|
||||
const documentRef = typeof filters.document_ref === "string" ? filters.document_ref.trim() : "";
|
||||
if (intent === "account_balance_snapshot" || intent === "documents_forming_balance") {
|
||||
if (account) {
|
||||
@@ -218,6 +219,15 @@ function resolvePrimaryAnchor(intent, filters) {
|
||||
ambiguity_count: 0
|
||||
};
|
||||
}
|
||||
if (organization) {
|
||||
return {
|
||||
anchor_type: "organization",
|
||||
anchor_value_raw: organization,
|
||||
anchor_value_resolved: organization,
|
||||
resolver_confidence: "medium",
|
||||
ambiguity_count: 0
|
||||
};
|
||||
}
|
||||
if (documentRef) {
|
||||
return {
|
||||
anchor_type: "document_ref",
|
||||
@@ -242,15 +252,24 @@ function refineAnchorFromRows(anchor, rows) {
|
||||
if (anchor.anchor_type !== "counterparty" &&
|
||||
anchor.anchor_type !== "contract" &&
|
||||
anchor.anchor_type !== "item" &&
|
||||
anchor.anchor_type !== "warehouse") {
|
||||
anchor.anchor_type !== "warehouse" &&
|
||||
anchor.anchor_type !== "organization") {
|
||||
return anchor;
|
||||
}
|
||||
const needleRaw = String(anchor.anchor_value_raw ?? "").trim();
|
||||
if (!needleRaw) {
|
||||
return anchor;
|
||||
}
|
||||
const searchableRows = anchor.anchor_type === "item" || anchor.anchor_type === "warehouse"
|
||||
? rows.flatMap((row) => [row.registrator, row.item ?? "", row.warehouse ?? "", row.account_dt ?? "", row.account_kt ?? "", ...row.analytics])
|
||||
const searchableRows = anchor.anchor_type === "item" || anchor.anchor_type === "warehouse" || anchor.anchor_type === "organization"
|
||||
? rows.flatMap((row) => [
|
||||
row.registrator,
|
||||
row.item ?? "",
|
||||
row.warehouse ?? "",
|
||||
row.organization ?? "",
|
||||
row.account_dt ?? "",
|
||||
row.account_kt ?? "",
|
||||
...row.analytics
|
||||
])
|
||||
: rows.flatMap((row) => row.analytics);
|
||||
const candidates = uniqueStrings(searchableRows
|
||||
.map((value) => value.trim())
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.normalizeAddressLlmSemanticHints = normalizeAddressLlmSemanticHints;
|
||||
exports.applyAddressLlmSemanticHintsToExtraction = applyAddressLlmSemanticHintsToExtraction;
|
||||
function toNonEmptyString(value) {
|
||||
if (value === null || value === undefined) {
|
||||
return null;
|
||||
}
|
||||
const normalized = String(value).trim();
|
||||
return normalized.length > 0 ? normalized : null;
|
||||
}
|
||||
function normalizeToken(value) {
|
||||
return String(value ?? "")
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, "_");
|
||||
}
|
||||
function normalizeAddressLlmSemanticHints(value) {
|
||||
if (!value || typeof value !== "object") {
|
||||
return null;
|
||||
}
|
||||
const source = value;
|
||||
const scopeToken = normalizeToken(source.scope_target_kind);
|
||||
const dateToken = normalizeToken(source.date_scope_kind);
|
||||
const scopeTargetKind = scopeToken === "self_scope" ||
|
||||
scopeToken === "selected_object" ||
|
||||
scopeToken === "organization" ||
|
||||
scopeToken === "warehouse" ||
|
||||
scopeToken === "counterparty" ||
|
||||
scopeToken === "contract" ||
|
||||
scopeToken === "item"
|
||||
? scopeToken
|
||||
: "none";
|
||||
const dateScopeKind = dateToken === "explicit" || dateToken === "implicit_current" ? dateToken : "missing";
|
||||
return {
|
||||
scope_target_kind: scopeTargetKind,
|
||||
scope_target_text: toNonEmptyString(source.scope_target_text),
|
||||
date_scope_kind: dateScopeKind,
|
||||
self_scope_detected: source.self_scope_detected === true || scopeTargetKind === "self_scope",
|
||||
selected_object_scope_detected: source.selected_object_scope_detected === true || scopeTargetKind === "selected_object"
|
||||
};
|
||||
}
|
||||
function defaultSemanticFrame(extraction) {
|
||||
return (extraction.semantic_frame ?? {
|
||||
scope_kind: "none",
|
||||
anchor_kind: "none",
|
||||
anchor_value: null,
|
||||
date_scope_kind: "none",
|
||||
date_basis_hint: null,
|
||||
self_scope_detected: false,
|
||||
selected_object_scope_detected: false
|
||||
});
|
||||
}
|
||||
function pushWarning(warnings, value) {
|
||||
if (!warnings.includes(value)) {
|
||||
warnings.push(value);
|
||||
}
|
||||
}
|
||||
function applyDateScopeHint(frame, dateScopeKind) {
|
||||
if (dateScopeKind === "explicit") {
|
||||
frame.date_scope_kind = "explicit";
|
||||
return;
|
||||
}
|
||||
if (dateScopeKind === "implicit_current" && frame.date_scope_kind !== "explicit") {
|
||||
frame.date_scope_kind = "implicit_current";
|
||||
frame.date_basis_hint = "implicit_current_snapshot";
|
||||
}
|
||||
}
|
||||
function applyAddressLlmSemanticHintsToExtraction(extraction, semanticHintsInput) {
|
||||
const semanticHints = normalizeAddressLlmSemanticHints(semanticHintsInput);
|
||||
if (!semanticHints) {
|
||||
return extraction;
|
||||
}
|
||||
const extractedFilters = { ...(extraction.extracted_filters ?? {}) };
|
||||
const warnings = [...(Array.isArray(extraction.warnings) ? extraction.warnings : [])];
|
||||
const semanticFrame = { ...defaultSemanticFrame(extraction) };
|
||||
const scopeTargetText = semanticHints.scope_target_text;
|
||||
applyDateScopeHint(semanticFrame, semanticHints.date_scope_kind);
|
||||
if (semanticHints.self_scope_detected) {
|
||||
semanticFrame.scope_kind = "implicit_self_scope";
|
||||
semanticFrame.anchor_kind = "self_scope";
|
||||
semanticFrame.anchor_value = null;
|
||||
semanticFrame.self_scope_detected = true;
|
||||
}
|
||||
if (semanticHints.selected_object_scope_detected) {
|
||||
if (semanticFrame.scope_kind === "none") {
|
||||
semanticFrame.scope_kind = "selected_object_scope";
|
||||
semanticFrame.anchor_kind = "selected_object";
|
||||
semanticFrame.anchor_value = null;
|
||||
}
|
||||
semanticFrame.selected_object_scope_detected = true;
|
||||
}
|
||||
if (semanticHints.scope_target_kind === "organization" && scopeTargetText) {
|
||||
extractedFilters.organization = scopeTargetText;
|
||||
pushWarning(warnings, "organization_from_llm_semantics");
|
||||
if (toNonEmptyString(extractedFilters.warehouse)) {
|
||||
delete extractedFilters.warehouse;
|
||||
pushWarning(warnings, "warehouse_cleared_by_llm_organization_semantics");
|
||||
}
|
||||
semanticFrame.scope_kind = "explicit_anchor";
|
||||
semanticFrame.anchor_kind = "organization";
|
||||
semanticFrame.anchor_value = scopeTargetText;
|
||||
}
|
||||
if (semanticHints.scope_target_kind === "warehouse" && scopeTargetText) {
|
||||
extractedFilters.warehouse = scopeTargetText;
|
||||
pushWarning(warnings, "warehouse_from_llm_semantics");
|
||||
semanticFrame.scope_kind = "explicit_anchor";
|
||||
semanticFrame.anchor_kind = "warehouse";
|
||||
semanticFrame.anchor_value = scopeTargetText;
|
||||
}
|
||||
if (semanticHints.scope_target_kind === "counterparty" && scopeTargetText) {
|
||||
extractedFilters.counterparty = scopeTargetText;
|
||||
pushWarning(warnings, "counterparty_from_llm_semantics");
|
||||
semanticFrame.scope_kind = "explicit_anchor";
|
||||
semanticFrame.anchor_kind = "counterparty";
|
||||
semanticFrame.anchor_value = scopeTargetText;
|
||||
}
|
||||
if (semanticHints.scope_target_kind === "contract" && scopeTargetText) {
|
||||
extractedFilters.contract = scopeTargetText;
|
||||
pushWarning(warnings, "contract_from_llm_semantics");
|
||||
semanticFrame.scope_kind = "explicit_anchor";
|
||||
semanticFrame.anchor_kind = "contract";
|
||||
semanticFrame.anchor_value = scopeTargetText;
|
||||
}
|
||||
if (semanticHints.scope_target_kind === "item" && scopeTargetText) {
|
||||
extractedFilters.item = scopeTargetText;
|
||||
pushWarning(warnings, "item_from_llm_semantics");
|
||||
semanticFrame.scope_kind = "explicit_anchor";
|
||||
semanticFrame.anchor_kind = "item";
|
||||
semanticFrame.anchor_value = scopeTargetText;
|
||||
}
|
||||
return {
|
||||
...extraction,
|
||||
extracted_filters: extractedFilters,
|
||||
warnings,
|
||||
semantic_frame: semanticFrame
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user