АРЧ АП11 - Архитектура после регресса: Архитектура: сделать profile exact-ответы business-first и убрать служебный aggregate-пreamble

This commit is contained in:
dctouch 2026-04-18 15:17:38 +03:00
parent d8ce2b7d88
commit 4bb0187510
4 changed files with 119 additions and 30 deletions

View File

@ -245,6 +245,10 @@ Latest continuity-authority convergence evidence after the current route pass:
- deterministic organization-fact boundary replies can now trigger from grounded continuity even when `sessionScope.selectedOrganization` and `sessionScope.activeOrganization` are both empty at runtime entry;
- the chat layer now records whether it entered with grounded continuity and which organization came from that continuity snapshot, making future saved-session review less blind;
- proactive organization offer logic is now explicitly blocked when grounded address continuity already exists, so the chat layer does not re-offer company selection on top of an already grounded business session;
- the first human-answer-shaping cleanup pass is now applied to heavy profile/aggregate exact answers:
- `period_coverage_profile` and `document_type_and_account_section_profile` now start with a direct business-first lead (`Коротко: ...`) instead of service-flavored intros like `профиль собран` / `строк агрегата`;
- the top block now states the business conclusion first and leaves ranked detail blocks below, which reduces the catalog-like feel without hiding the actual data;
- this is still only the first shaping seam: other long exact replies, especially VAT and some ranking families, still need the same treatment;
- this pass does not yet finish full single-owner continuity, but it narrows one of the remaining seams where route arbitration and scope memory could disagree about whether the session was still grounded.
## Next Execution Slice (2026-04-18)

View File

@ -2229,10 +2229,30 @@ function composeFactualReplyBody(intent, rows, options = {}) {
const includeSections = focus === "full_profile" || focus === "sections_only" || focus === "sections_rare_only";
const includeDocTypesLowOnly = focus === "doc_types_rare_only";
const includeSectionsLowOnly = focus === "sections_rare_only";
const lines = [
"Профиль типов документов и разделов учета собран (movement-based aggregate).",
`Строк агрегата: ${rows.length}.`
];
const topDocType = docTypeRanking[0] ?? null;
const rareDocType = docTypeRankingLow[0] ?? null;
const topSection = sectionRanking[0] ?? null;
const rareSection = sectionRankingLow[0] ?? null;
const formatSectionTitle = (section) => {
const label = ACCOUNT_SECTION_LABELS[section];
return label ? `${section} (${label})` : section;
};
const directLeadParts = [];
if (includeDocTypesLowOnly && rareDocType) {
directLeadParts.push(`реже всего используется тип документа ${rareDocType.docType} (${rareDocType.count})`);
}
else if (includeDocTypes && topDocType) {
directLeadParts.push(`чаще всего используется тип документа ${topDocType.docType} (${topDocType.count})`);
}
if (includeSectionsLowOnly && rareSection) {
directLeadParts.push(`среди разделов учета минимальная активность у ${formatSectionTitle(rareSection.section)} (${rareSection.count})`);
}
else if (includeSections && topSection) {
directLeadParts.push(`среди разделов учета лидирует ${formatSectionTitle(topSection.section)} (${topSection.count})`);
}
const lines = directLeadParts.length > 0
? [`Коротко: ${directLeadParts.join("; ")}.`]
: ["Коротко: по доступному срезу профиль типов документов и разделов учета не подтвердился."];
if (includeDocTypes) {
if (docTypeRanking.length > 0) {
if (includeDocTypesLowOnly) {
@ -2263,8 +2283,7 @@ function composeFactualReplyBody(intent, rows, options = {}) {
if (includeSectionsLowOnly) {
lines.push("Наименее заполненные разделы учета (по операциям Дт+Кт):");
lines.push(...sectionRankingLow.map((item, index) => {
const label = ACCOUNT_SECTION_LABELS[item.section];
const sectionTitle = label ? `${item.section} (${label})` : item.section;
const sectionTitle = formatSectionTitle(item.section);
const share = formatPercent(item.count, sectionTotal);
return share
? `${index + 1}. ${sectionTitle}: ${item.count} (${share})`
@ -2274,8 +2293,7 @@ function composeFactualReplyBody(intent, rows, options = {}) {
else {
lines.push("Наиболее заполненные разделы учета (по операциям Дт+Кт):");
lines.push(...sectionRanking.slice(0, 10).map((item, index) => {
const label = ACCOUNT_SECTION_LABELS[item.section];
const sectionTitle = label ? `${item.section} (${label})` : item.section;
const sectionTitle = formatSectionTitle(item.section);
const share = formatPercent(item.count, sectionTotal);
return share
? `${index + 1}. ${sectionTitle}: ${item.count} (${share})`
@ -2283,8 +2301,7 @@ function composeFactualReplyBody(intent, rows, options = {}) {
}));
lines.push("Разделы с минимальной активностью (среди использованных):");
lines.push(...sectionRankingLow.map((item, index) => {
const label = ACCOUNT_SECTION_LABELS[item.section];
const sectionTitle = label ? `${item.section} (${label})` : item.section;
const sectionTitle = formatSectionTitle(item.section);
return `${index + 1}. ${sectionTitle}: ${item.count}`;
}));
}
@ -2366,10 +2383,32 @@ function composeFactualReplyBody(intent, rows, options = {}) {
operationalWindow.operationalTo !== null &&
operationalWindow.dataTo !== null &&
operationalWindow.operationalTo < operationalWindow.dataTo;
const lines = [
"Профиль периодов базы собран (movement-based aggregate).",
`Строк агрегата: ${rows.length}.`
];
const directLeadParts = [];
if (includeCoverage) {
if (hasTailYears &&
operationalWindow.operationalFrom !== null &&
operationalWindow.operationalTo !== null) {
directLeadParts.push(`рабочий период с выраженной активностью: ${operationalWindow.operationalFrom}..${operationalWindow.operationalTo}`);
}
else if (minDate || maxDate) {
directLeadParts.push(`данные в базе подтверждены за период ${minDate ?? "н/д"} .. ${maxDate ?? "н/д"}`);
}
}
if (includeTopYear && topYearByDocs) {
directLeadParts.push(`самый активный год по документам — ${topYearByDocs.year} (${topYearByDocs.count})`);
}
if (includeBottomYear && bottomYearByDocs) {
directLeadParts.push(`самый спокойный год по документам — ${bottomYearByDocs.year} (${bottomYearByDocs.count})`);
}
if (includeTopMonth && topMonthByOps) {
directLeadParts.push(`самый активный месяц по операциям — ${topMonthByOps.month} (${topMonthByOps.count})`);
}
if (includeBottomMonth && bottomMonthByOps) {
directLeadParts.push(`самый спокойный месяц по операциям — ${bottomMonthByOps.month} (${bottomMonthByOps.count})`);
}
const lines = directLeadParts.length > 0
? [`Коротко: ${directLeadParts.join("; ")}.`]
: ["Коротко: по доступному срезу периодический профиль базы не подтвердился."];
if (includeCoverage) {
if (hasTailYears &&
operationalWindow.operationalFrom !== null &&

View File

@ -2861,11 +2861,30 @@ function composeFactualReplyBody(
focus === "full_profile" || focus === "sections_only" || focus === "sections_rare_only";
const includeDocTypesLowOnly = focus === "doc_types_rare_only";
const includeSectionsLowOnly = focus === "sections_rare_only";
const topDocType = docTypeRanking[0] ?? null;
const rareDocType = docTypeRankingLow[0] ?? null;
const topSection = sectionRanking[0] ?? null;
const rareSection = sectionRankingLow[0] ?? null;
const formatSectionTitle = (section: string): string => {
const label = ACCOUNT_SECTION_LABELS[section];
return label ? `${section} (${label})` : section;
};
const lines: string[] = [
"Профиль типов документов и разделов учета собран (movement-based aggregate).",
`Строк агрегата: ${rows.length}.`
];
const directLeadParts: string[] = [];
if (includeDocTypesLowOnly && rareDocType) {
directLeadParts.push(`реже всего используется тип документа ${rareDocType.docType} (${rareDocType.count})`);
} else if (includeDocTypes && topDocType) {
directLeadParts.push(`чаще всего используется тип документа ${topDocType.docType} (${topDocType.count})`);
}
if (includeSectionsLowOnly && rareSection) {
directLeadParts.push(`среди разделов учета минимальная активность у ${formatSectionTitle(rareSection.section)} (${rareSection.count})`);
} else if (includeSections && topSection) {
directLeadParts.push(`среди разделов учета лидирует ${formatSectionTitle(topSection.section)} (${topSection.count})`);
}
const lines: string[] = directLeadParts.length > 0
? [`Коротко: ${directLeadParts.join("; ")}.`]
: ["Коротко: по доступному срезу профиль типов документов и разделов учета не подтвердился."];
if (includeDocTypes) {
if (docTypeRanking.length > 0) {
@ -2901,8 +2920,7 @@ function composeFactualReplyBody(
lines.push("Наименее заполненные разделы учета (по операциям Дт+Кт):");
lines.push(
...sectionRankingLow.map((item, index) => {
const label = ACCOUNT_SECTION_LABELS[item.section];
const sectionTitle = label ? `${item.section} (${label})` : item.section;
const sectionTitle = formatSectionTitle(item.section);
const share = formatPercent(item.count, sectionTotal);
return share
? `${index + 1}. ${sectionTitle}: ${item.count} (${share})`
@ -2913,8 +2931,7 @@ function composeFactualReplyBody(
lines.push("Наиболее заполненные разделы учета (по операциям Дт+Кт):");
lines.push(
...sectionRanking.slice(0, 10).map((item, index) => {
const label = ACCOUNT_SECTION_LABELS[item.section];
const sectionTitle = label ? `${item.section} (${label})` : item.section;
const sectionTitle = formatSectionTitle(item.section);
const share = formatPercent(item.count, sectionTotal);
return share
? `${index + 1}. ${sectionTitle}: ${item.count} (${share})`
@ -2925,8 +2942,7 @@ function composeFactualReplyBody(
lines.push("Разделы с минимальной активностью (среди использованных):");
lines.push(
...sectionRankingLow.map((item, index) => {
const label = ACCOUNT_SECTION_LABELS[item.section];
const sectionTitle = label ? `${item.section} (${label})` : item.section;
const sectionTitle = formatSectionTitle(item.section);
return `${index + 1}. ${sectionTitle}: ${item.count}`;
})
);
@ -3024,10 +3040,36 @@ function composeFactualReplyBody(
operationalWindow.dataTo !== null &&
operationalWindow.operationalTo < operationalWindow.dataTo;
const lines: string[] = [
"Профиль периодов базы собран (movement-based aggregate).",
`Строк агрегата: ${rows.length}.`
];
const directLeadParts: string[] = [];
if (includeCoverage) {
if (
hasTailYears &&
operationalWindow.operationalFrom !== null &&
operationalWindow.operationalTo !== null
) {
directLeadParts.push(
`рабочий период с выраженной активностью: ${operationalWindow.operationalFrom}..${operationalWindow.operationalTo}`
);
} else if (minDate || maxDate) {
directLeadParts.push(`данные в базе подтверждены за период ${minDate ?? "н/д"} .. ${maxDate ?? "н/д"}`);
}
}
if (includeTopYear && topYearByDocs) {
directLeadParts.push(`самый активный год по документам — ${topYearByDocs.year} (${topYearByDocs.count})`);
}
if (includeBottomYear && bottomYearByDocs) {
directLeadParts.push(`самый спокойный год по документам — ${bottomYearByDocs.year} (${bottomYearByDocs.count})`);
}
if (includeTopMonth && topMonthByOps) {
directLeadParts.push(`самый активный месяц по операциям — ${topMonthByOps.month} (${topMonthByOps.count})`);
}
if (includeBottomMonth && bottomMonthByOps) {
directLeadParts.push(`самый спокойный месяц по операциям — ${bottomMonthByOps.month} (${bottomMonthByOps.count})`);
}
const lines: string[] = directLeadParts.length > 0
? [`Коротко: ${directLeadParts.join("; ")}.`]
: ["Коротко: по доступному срезу периодический профиль базы не подтвердился."];
if (includeCoverage) {
if (

View File

@ -628,9 +628,11 @@ describe("address compose stage utf8 headers", () => {
}
]);
expect(reply.responseType).toBe("FACTUAL_SUMMARY");
expect(reply.text).toContain("Профиль периодов базы собран");
expect(reply.text).toContain("Коротко:");
expect(reply.text).toContain("Самый активный год по документам: 2019 (1004).");
expect(reply.text).toContain("Самый активный месяц по операциям: 2015-02 (1249).");
expect(reply.text).not.toContain("Строк агрегата");
expect(reply.text).not.toContain("movement-based aggregate");
});
it("renders document type + account section profile summary", () => {
@ -685,10 +687,12 @@ describe("address compose stage utf8 headers", () => {
}
]);
expect(reply.responseType).toBe("FACTUAL_SUMMARY");
expect(reply.text).toContain("Профиль типов документов и разделов учета собран");
expect(reply.text).toContain("Коротко:");
expect(reply.text).toContain("Списание с расчетного счета: 2352");
expect(reply.text).toContain("90 (Продажи): 2973");
expect(reply.text).toContain("58 (Финансовые вложения): 2");
expect(reply.text).not.toContain("Строк агрегата");
expect(reply.text).not.toContain("movement-based aggregate");
});
it("returns focused answer for active year question (without month block)", () => {