ARCH: выровнять ответ с текущим смыслом оборота контрагента
This commit is contained in:
parent
f75be32e41
commit
f7b378ebc5
|
|
@ -20,6 +20,16 @@ function groupRowsByMarker(rows) {
|
||||||
function formatOptionalDate(value, formatDateRu) {
|
function formatOptionalDate(value, formatDateRu) {
|
||||||
return value ? formatDateRu(value) : "дата не указана";
|
return value ? formatDateRu(value) : "дата не указана";
|
||||||
}
|
}
|
||||||
|
function findFocusedCounterpartyValuePoint(profileRows, counterpartyHint, deps) {
|
||||||
|
if (!counterpartyHint) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const matched = profileRows.find((item) => deps.counterpartyLookupMatches(item.name, counterpartyHint));
|
||||||
|
if (matched) {
|
||||||
|
return matched;
|
||||||
|
}
|
||||||
|
return profileRows.length === 1 ? profileRows[0] : null;
|
||||||
|
}
|
||||||
function composeCounterpartyAnalyticsReply(intent, rows, options = {}, deps) {
|
function composeCounterpartyAnalyticsReply(intent, rows, options = {}, deps) {
|
||||||
if (intent === "counterparty_population_and_roles") {
|
if (intent === "counterparty_population_and_roles") {
|
||||||
const rowsByMarker = groupRowsByMarker(rows);
|
const rowsByMarker = groupRowsByMarker(rows);
|
||||||
|
|
@ -459,6 +469,31 @@ function composeCounterpartyAnalyticsReply(intent, rows, options = {}, deps) {
|
||||||
lines.push("По выбранному окну данных платежные строки не найдены.");
|
lines.push("По выбранному окну данных платежные строки не найдены.");
|
||||||
return (0, replyContracts_1.buildFactualSummaryReply)(lines);
|
return (0, replyContracts_1.buildFactualSummaryReply)(lines);
|
||||||
}
|
}
|
||||||
|
const focusedCounterparty = focus === "top_by_total" || focus === "total_flow"
|
||||||
|
? findFocusedCounterpartyValuePoint(profileRows, options.counterpartyHint, deps)
|
||||||
|
: null;
|
||||||
|
if (focusedCounterparty) {
|
||||||
|
const periodLabel = options.periodFrom && options.periodTo
|
||||||
|
? `за период ${deps.formatDateRu(options.periodFrom)}..${deps.formatDateRu(options.periodTo)}`
|
||||||
|
: "за доступное время";
|
||||||
|
const directAnswerLine = isSupplier
|
||||||
|
? `Оборот по ${focusedCounterparty.name} ${periodLabel}: ${deps.formatMoneyRub(focusedCounterparty.total)} по ${focusedCounterparty.ops} подтвержденным исходящим операциям. Это денежный поток по поставщику, а не итоговая задолженность.`
|
||||||
|
: `Оборот по ${focusedCounterparty.name} ${periodLabel}: ${deps.formatMoneyRub(focusedCounterparty.total)} по ${focusedCounterparty.ops} подтвержденным входящим операциям. Это денежный поток от клиента, а не чистая прибыль.`;
|
||||||
|
const summaryLines = [
|
||||||
|
directAnswerLine,
|
||||||
|
"",
|
||||||
|
"Подтверждение:",
|
||||||
|
`- Контрагент в выборке: ${focusedCounterparty.name}.`,
|
||||||
|
`- Операций: ${focusedCounterparty.ops}.`
|
||||||
|
];
|
||||||
|
if (focusedCounterparty.lastPeriod) {
|
||||||
|
summaryLines.push(`- Последняя подтвержденная операция: ${deps.formatDateRu(focusedCounterparty.lastPeriod)}.`);
|
||||||
|
}
|
||||||
|
if (profileRows.length > 1) {
|
||||||
|
summaryLines.push(`- Всего контрагентов в срезе: ${profileRows.length}.`);
|
||||||
|
}
|
||||||
|
return (0, replyContracts_1.buildFactualSummaryReply)(summaryLines);
|
||||||
|
}
|
||||||
if (focus === "total_flow") {
|
if (focus === "total_flow") {
|
||||||
const periodLine = options.periodFrom && options.periodTo
|
const periodLine = options.periodFrom && options.periodTo
|
||||||
? `За период ${deps.formatDateRu(options.periodFrom)}..${deps.formatDateRu(options.periodTo)} подтверждено ${deps.formatMoneyRub(totalFlow)} ${isSupplier ? "исходящих выплат" : "входящих поступлений"}.`
|
? `За период ${deps.formatDateRu(options.periodFrom)}..${deps.formatDateRu(options.periodTo)} подтверждено ${deps.formatMoneyRub(totalFlow)} ${isSupplier ? "исходящих выплат" : "входящих поступлений"}.`
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,21 @@ function formatOptionalDate(value: string | null, formatDateRu: (isoDate: string
|
||||||
return value ? formatDateRu(value) : "дата не указана";
|
return value ? formatDateRu(value) : "дата не указана";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findFocusedCounterpartyValuePoint(
|
||||||
|
profileRows: CounterpartyValuePoint[],
|
||||||
|
counterpartyHint: string | null | undefined,
|
||||||
|
deps: Pick<CounterpartyAnalyticsReplyDeps, "counterpartyLookupMatches">
|
||||||
|
): CounterpartyValuePoint | null {
|
||||||
|
if (!counterpartyHint) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const matched = profileRows.find((item) => deps.counterpartyLookupMatches(item.name, counterpartyHint));
|
||||||
|
if (matched) {
|
||||||
|
return matched;
|
||||||
|
}
|
||||||
|
return profileRows.length === 1 ? profileRows[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
export function composeCounterpartyAnalyticsReply(
|
export function composeCounterpartyAnalyticsReply(
|
||||||
intent: AddressIntent,
|
intent: AddressIntent,
|
||||||
rows: ComposeStageRow[],
|
rows: ComposeStageRow[],
|
||||||
|
|
@ -605,6 +620,34 @@ export function composeCounterpartyAnalyticsReply(
|
||||||
return buildFactualSummaryReply(lines);
|
return buildFactualSummaryReply(lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const focusedCounterparty =
|
||||||
|
focus === "top_by_total" || focus === "total_flow"
|
||||||
|
? findFocusedCounterpartyValuePoint(profileRows, options.counterpartyHint, deps)
|
||||||
|
: null;
|
||||||
|
if (focusedCounterparty) {
|
||||||
|
const periodLabel =
|
||||||
|
options.periodFrom && options.periodTo
|
||||||
|
? `за период ${deps.formatDateRu(options.periodFrom)}..${deps.formatDateRu(options.periodTo)}`
|
||||||
|
: "за доступное время";
|
||||||
|
const directAnswerLine = isSupplier
|
||||||
|
? `Оборот по ${focusedCounterparty.name} ${periodLabel}: ${deps.formatMoneyRub(focusedCounterparty.total)} по ${focusedCounterparty.ops} подтвержденным исходящим операциям. Это денежный поток по поставщику, а не итоговая задолженность.`
|
||||||
|
: `Оборот по ${focusedCounterparty.name} ${periodLabel}: ${deps.formatMoneyRub(focusedCounterparty.total)} по ${focusedCounterparty.ops} подтвержденным входящим операциям. Это денежный поток от клиента, а не чистая прибыль.`;
|
||||||
|
const summaryLines = [
|
||||||
|
directAnswerLine,
|
||||||
|
"",
|
||||||
|
"Подтверждение:",
|
||||||
|
`- Контрагент в выборке: ${focusedCounterparty.name}.`,
|
||||||
|
`- Операций: ${focusedCounterparty.ops}.`
|
||||||
|
];
|
||||||
|
if (focusedCounterparty.lastPeriod) {
|
||||||
|
summaryLines.push(`- Последняя подтвержденная операция: ${deps.formatDateRu(focusedCounterparty.lastPeriod)}.`);
|
||||||
|
}
|
||||||
|
if (profileRows.length > 1) {
|
||||||
|
summaryLines.push(`- Всего контрагентов в срезе: ${profileRows.length}.`);
|
||||||
|
}
|
||||||
|
return buildFactualSummaryReply(summaryLines);
|
||||||
|
}
|
||||||
|
|
||||||
if (focus === "total_flow") {
|
if (focus === "total_flow") {
|
||||||
const periodLine =
|
const periodLine =
|
||||||
options.periodFrom && options.periodTo
|
options.periodFrom && options.periodTo
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,41 @@ describe("counterparty analytics reply builders", () => {
|
||||||
expect(reply.text).not.toContain("max single");
|
expect(reply.text).not.toContain("max single");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("answers specific counterparty turnover directly instead of ranking", () => {
|
||||||
|
const reply = composeFactualReply(
|
||||||
|
"customer_revenue_and_payments",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
period: "2024-02-01T00:00:00Z",
|
||||||
|
registrator: "Поступление 1",
|
||||||
|
account_dt: "",
|
||||||
|
account_kt: "",
|
||||||
|
amount: 1000,
|
||||||
|
analytics: ["СВК", "Договор СВК-1"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
period: "2024-02-15T00:00:00Z",
|
||||||
|
registrator: "Поступление 2",
|
||||||
|
account_dt: "",
|
||||||
|
account_kt: "",
|
||||||
|
amount: 2500,
|
||||||
|
analytics: ["СВК", "Договор СВК-1"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
{
|
||||||
|
userMessage: "какой оборот был свк",
|
||||||
|
counterpartyHint: "свк"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(reply.responseType).toBe("FACTUAL_SUMMARY");
|
||||||
|
expect(reply.text).toContain("Оборот по СВК за доступное время: 3.500,00");
|
||||||
|
expect(reply.text).toContain("по 2 подтвержденным входящим операциям");
|
||||||
|
expect(reply.text).toContain("Это денежный поток от клиента, а не чистая прибыль");
|
||||||
|
expect(reply.text).not.toContain("Самый доходный клиент");
|
||||||
|
expect(reply.text).not.toContain("Топ-");
|
||||||
|
});
|
||||||
|
|
||||||
it("explains organization activity age as 1C activity rather than legal age", () => {
|
it("explains organization activity age as 1C activity rather than legal age", () => {
|
||||||
const reply = composeFactualReply(
|
const reply = composeFactualReply(
|
||||||
"counterparty_activity_lifecycle",
|
"counterparty_activity_lifecycle",
|
||||||
|
|
@ -132,7 +167,8 @@ describe("counterparty analytics reply builders", () => {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(reply.responseType).toBe("FACTUAL_SUMMARY");
|
expect(reply.responseType).toBe("FACTUAL_SUMMARY");
|
||||||
expect(reply.text).toContain("Профиль договорной базы собран по справочнику и подтвержденным операциям.");
|
expect(reply.text).toContain("Коротко: Использованных договоров: 148 из 520 (28.5%).");
|
||||||
|
expect(reply.text).toContain("Что видно по договорной базе: 2 строк в подтвержденной выборке.");
|
||||||
expect(reply.text).toContain("Использованных договоров с подтвержденной связью с операциями: 148.");
|
expect(reply.text).toContain("Использованных договоров с подтвержденной связью с операциями: 148.");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue