АРЧ АП11 - Архитектура после регресса: + Архитектура: восстановить bridge от provenance выбранной позиции к VAT-периоду и закрыть phase10 replay

This commit is contained in:
2026-04-18 11:50:48 +03:00
parent 9872ef5446
commit 31cb4ccbbb
27 changed files with 1655 additions and 49 deletions
@@ -639,13 +639,18 @@ export function composeCounterpartyAnalyticsReply(
if (focus === "top_years_by_total") {
const visible = rankedByYearTotal.slice(0, limit);
const heading = isSupplier
? `Топ-${visible.length} лет по сумме выплат:`
: `Топ-${visible.length} лет по сумме поступлений:`;
lines.unshift(heading);
if (visible.length === 0) {
lines.push("По доступному окну не удалось собрать годовые агрегаты по суммам.");
} else {
const strongestYear = visible[0];
const directAnswerLine = isSupplier
? `Самый крупный год по подтвержденным выплатам: ${strongestYear.year} (${deps.formatMoneyRub(strongestYear.total)} по ${strongestYear.ops} операциям).`
: `Самый доходный год по подтвержденным поступлениям: ${strongestYear.year} (${deps.formatMoneyRub(strongestYear.total)} по ${strongestYear.ops} операциям). Это денежный поток, а не чистая прибыль.`;
const heading = isSupplier
? `Топ-${visible.length} лет по сумме выплат:`
: `Топ-${visible.length} лет по сумме поступлений:`;
lines.unshift(heading);
lines.unshift(directAnswerLine);
lines.push(
...visible.map(
(item, index) =>
@@ -742,7 +747,14 @@ export function composeCounterpartyAnalyticsReply(
const heading = isSupplier
? `Топ-${visible.length} поставщиков по сумме выплат:`
: `Топ-${visible.length} заказчиков по сумме поступлений:`;
const leadingCounterparty = visible[0] ?? null;
lines.unshift(heading);
if (leadingCounterparty) {
const directAnswerLine = isSupplier
? `Крупнейший поставщик по подтвержденным выплатам за доступное время: ${leadingCounterparty.name} (${deps.formatMoneyRub(leadingCounterparty.total)} по ${leadingCounterparty.ops} операциям).`
: `Самый доходный клиент за доступное время по подтвержденным поступлениям: ${leadingCounterparty.name} (${deps.formatMoneyRub(leadingCounterparty.total)} по ${leadingCounterparty.ops} операциям). Это денежный поток, а не чистая прибыль.`;
lines.unshift(directAnswerLine);
}
lines.push(
...visible.map((item, index) => {
const avgCheck = item.ops > 0 ? item.total / item.ops : 0;
@@ -691,6 +691,19 @@ export function hasInventoryPurchaseToSaleChainFollowupCue(text: string): boolea
);
}
export function hasInventoryPurchaseDateVatBridgeCue(text: string): boolean {
const normalized = String(text ?? "").trim();
if (!normalized) {
return false;
}
return (
/(?:ндс|vat)/iu.test(normalized) &&
/(?:на\s+дат[ауеы]\s+покупк|на\s+дат[ауеы]\s+закупк|по\s+дат[еу]\s+покупк|по\s+дат[еу]\s+закупк|дата\s+покупк|дата\s+закупк|purchase\s+date)/iu.test(
normalized
)
);
}
export function hasAddressFollowupContextSignal(text: string): boolean {
const normalized = String(text ?? "").trim();
if (!normalized) {
@@ -728,6 +741,9 @@ export function hasAddressFollowupContextSignal(text: string): boolean {
) {
return true;
}
if (tokenCount <= 14 && hasInventoryPurchaseDateVatBridgeCue(normalized)) {
return true;
}
const hasPeriodLiteral = /\b(?:19|20)\d{2}(?:[./-](?:0?[1-9]|1[0-2]))?\b/.test(normalized);
if (tokenCount <= 8 && hasPeriodLiteral) {
return true;
@@ -1314,6 +1330,21 @@ function deriveIntentWithFollowupContext(
const previousIsInventoryFamily = isInventoryIntent(sourceIntent ?? undefined);
const inventorySelectedObjectFollowup =
hasSelectedObjectInventorySignal(normalizedMessage) || (previousIsInventoryFamily && hasFollowupSignal);
const inventoryPurchaseDateVatBridge =
inventorySelectedObjectFollowup && hasInventoryPurchaseDateVatBridgeCue(normalizedMessage);
if (
inventoryPurchaseDateVatBridge &&
(detectedIntent.intent === "unknown" ||
detectedIntent.intent === sourceIntent ||
detectedIntent.intent === "vat_payable_confirmed_as_of_date")
) {
return {
intent: "vat_liability_confirmed_for_tax_period",
confidence: "low",
reasons: [...detectedIntent.reasons, "intent_adjusted_to_inventory_purchase_date_vat_bridge"]
};
}
if (detectedIntent.intent === "unknown" && isVatFollowup) {
const vatIntent: AddressIntent = hasVatTaxPaymentCue(normalizedMessage)
@@ -316,8 +316,8 @@ export function composeInventoryReply(
.join("; ");
const directAnswerLine =
agingItems.length > 0
? `К старым закупкам на ${deps.formatDateRu(asOfDate)} в первую очередь относятся позиции с самой ранней первой закупкой: ${oldestAnswerPreview}.`
: `По доступному закупочному следу на ${deps.formatDateRu(asOfDate)} позиции старых закупок не материализованы.`;
? `К самым старым закупкам в текущем подтвержденном срезе относятся позиции с самой ранней первой закупкой: ${oldestAnswerPreview}.`
: "По доступному закупочному следу позиции со старыми закупками не материализованы.";
const lines: string[] = [directAnswerLine];
appendInventoryBulletSection(lines, "Сводка:", [
`Дата среза: ${deps.formatDateRu(asOfDate)}.`,