АРЧ АП11 - Архитектура: вынести reply packaging из composeStage в отдельный owner
This commit is contained in:
parent
18e4eba54e
commit
c2bafcff9b
|
|
@ -25,15 +25,15 @@ This snapshot is based on:
|
||||||
|
|
||||||
Latest graph rebuild:
|
Latest graph rebuild:
|
||||||
|
|
||||||
- `5228 nodes`
|
- `5251 nodes`
|
||||||
- `11338 edges`
|
- `11337 edges`
|
||||||
- `133 communities`
|
- `136 communities`
|
||||||
|
|
||||||
Most relevant current god nodes for turnaround `11`:
|
Most relevant current god nodes for turnaround `11`:
|
||||||
|
|
||||||
1. `resolveAddressIntent()`
|
1. `resolveAddressIntent()`
|
||||||
2. `ChannelRegistry`
|
2. `composeFactualReplyBody()`
|
||||||
3. `composeFactualReply()`
|
3. `ChannelRegistry`
|
||||||
4. `CanonicalStore`
|
4. `CanonicalStore`
|
||||||
5. `compactWhitespace()`
|
5. `compactWhitespace()`
|
||||||
|
|
||||||
|
|
@ -42,7 +42,7 @@ The relevant conclusion is not that every god node is part of turnaround `11`.
|
||||||
The relevant conclusion is:
|
The relevant conclusion is:
|
||||||
|
|
||||||
- `resolveAddressIntent()` remains the main unresolved domain-intent concentration point;
|
- `resolveAddressIntent()` remains the main unresolved domain-intent concentration point;
|
||||||
- `composeFactualReply()` remains the main unresolved answer-shaping concentration point;
|
- `composeFactualReplyBody()` now carries the remaining answer-shaping concentration after packaging extraction;
|
||||||
- `assistantService` still appears as a large coordinator-heavy community rather than a thin shell.
|
- `assistantService` still appears as a large coordinator-heavy community rather than a thin shell.
|
||||||
|
|
||||||
## What Is Already Real In Code
|
## What Is Already Real In Code
|
||||||
|
|
@ -126,7 +126,7 @@ This is enough to build targeted semantic packs that are not single-domain toy s
|
||||||
|
|
||||||
## Honest Phase Status
|
## Honest Phase Status
|
||||||
|
|
||||||
Estimated overall turnaround completion: `~87%`
|
Estimated overall turnaround completion: `~88%`
|
||||||
|
|
||||||
### Phase 0. Shared Baseline
|
### Phase 0. Shared Baseline
|
||||||
|
|
||||||
|
|
@ -177,17 +177,19 @@ Remaining debt:
|
||||||
|
|
||||||
### Phase 4. Coverage / Evidence / Truth Gate Isolation
|
### Phase 4. Coverage / Evidence / Truth Gate Isolation
|
||||||
|
|
||||||
Status: `84%`
|
Status: `86%`
|
||||||
|
|
||||||
Reason:
|
Reason:
|
||||||
|
|
||||||
- explicit truth and coverage/evidence contracts exist;
|
- explicit truth and coverage/evidence contracts exist;
|
||||||
- answer policy reads those contracts rather than rebuilding verdicts blindly from raw rows.
|
- answer policy reads those contracts rather than rebuilding verdicts blindly from raw rows.
|
||||||
|
- reply-packaging mechanics are now explicitly split into `address_runtime/replyPackaging.ts` instead of staying fully in `composeStage.ts`.
|
||||||
|
|
||||||
Remaining debt:
|
Remaining debt:
|
||||||
|
|
||||||
- `composeFactualReply()` is still a major concentration point;
|
- `composeFactualReplyBody()` is still a major concentration point;
|
||||||
- humanized blocked/limited semantics are not yet fully separated from final packaging logic across all paths.
|
- humanized blocked/limited semantics are not yet fully separated from answer semantics across all paths;
|
||||||
|
- `composeStage.ts` still remains too large even after packaging extraction.
|
||||||
|
|
||||||
### Phase 5. AssistantService Extraction
|
### Phase 5. AssistantService Extraction
|
||||||
|
|
||||||
|
|
@ -244,6 +246,7 @@ Compared with the pre-turnaround baseline, the system is now materially better i
|
||||||
- meta questions and memory recap are no longer purely incidental side effects of route logic;
|
- meta questions and memory recap are no longer purely incidental side effects of route logic;
|
||||||
- organization data-scope probing is no longer owned only by coordinator-local helper bodies;
|
- organization data-scope probing is no longer owned only by coordinator-local helper bodies;
|
||||||
- debug payload assembly is now further isolated from top-level turn coordination;
|
- debug payload assembly is now further isolated from top-level turn coordination;
|
||||||
|
- reply formatting and reply-type classification now have an explicit owner outside `composeStage.ts`;
|
||||||
- architecture regressions can now be localized to route, transition, truth gate, coverage/evidence, boundary, or meta/memory layers.
|
- architecture regressions can now be localized to route, transition, truth gate, coverage/evidence, boundary, or meta/memory layers.
|
||||||
|
|
||||||
## What Still Remains The Main Architectural Debt
|
## What Still Remains The Main Architectural Debt
|
||||||
|
|
@ -258,9 +261,9 @@ Intent resolution remains one of the most connected business nodes in the graph.
|
||||||
|
|
||||||
This means capability and contour growth still concentrate pressure there.
|
This means capability and contour growth still concentrate pressure there.
|
||||||
|
|
||||||
### 3. `composeFactualReply()` is still too central
|
### 3. `composeFactualReplyBody()` is still too central
|
||||||
|
|
||||||
Truth contracts are now explicit, but final answer-shaping still retains too much architecture weight.
|
Truth contracts are now explicit, and reply packaging has started moving into its own owner, but final answer-shaping still retains too much architecture weight.
|
||||||
|
|
||||||
This is the main remaining reason why user-facing humanization and limitation semantics are not completely isolated yet.
|
This is the main remaining reason why user-facing humanization and limitation semantics are not completely isolated yet.
|
||||||
|
|
||||||
|
|
@ -281,7 +284,7 @@ But not every business family has reached the same contract maturity.
|
||||||
The next honest architecture slice should be:
|
The next honest architecture slice should be:
|
||||||
|
|
||||||
1. continue reducing `assistantService.ts` to a thinner coordinator;
|
1. continue reducing `assistantService.ts` to a thinner coordinator;
|
||||||
2. isolate answer-shaping semantics further away from `composeFactualReply()`;
|
2. continue isolating answer semantics further away from `composeFactualReply()` now that reply packaging has its own owner seam;
|
||||||
3. keep extending AGENT packs with mixed business + meta + interruption patterns instead of single-family smoke tests;
|
3. keep extending AGENT packs with mixed business + meta + interruption patterns instead of single-family smoke tests;
|
||||||
4. keep using scenario acceptance as the main sign-off rather than unit-test green status alone.
|
4. keep using scenario acceptance as the main sign-off rather than unit-test green status alone.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ exports.contractCandidatesFromRows = contractCandidatesFromRows;
|
||||||
exports.composeFactualReply = composeFactualReply;
|
exports.composeFactualReply = composeFactualReply;
|
||||||
exports.inferReplyType = inferReplyType;
|
exports.inferReplyType = inferReplyType;
|
||||||
const assistantOrganizationMatcher_1 = require("../assistantOrganizationMatcher");
|
const assistantOrganizationMatcher_1 = require("../assistantOrganizationMatcher");
|
||||||
|
const replyPackaging_1 = require("./replyPackaging");
|
||||||
function uniqueStrings(values) {
|
function uniqueStrings(values) {
|
||||||
return Array.from(new Set(values
|
return Array.from(new Set(values
|
||||||
.map((item) => item.trim())
|
.map((item) => item.trim())
|
||||||
|
|
@ -2106,8 +2107,13 @@ function buildOpenContractRiskAggregate(rows) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function composeFactualReply(intent, rows, options = {}) {
|
function composeFactualReply(intent, rows, options = {}) {
|
||||||
const applyNumericEmphasis = (line) => (options.emphasizeNumbers ? emphasizeNumericTokens(line) : line);
|
return (0, replyPackaging_1.finalizeComposeReplyResult)(composeFactualReplyBody(intent, rows, options), {
|
||||||
const joinLines = (lines) => lines.map(applyNumericEmphasis).join("\n");
|
emphasizeNumbers: options.emphasizeNumbers,
|
||||||
|
emphasizeNumericTokens
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function composeFactualReplyBody(intent, rows, options = {}) {
|
||||||
|
const joinLines = replyPackaging_1.joinComposeReplyLines;
|
||||||
if (intent === "document_type_and_account_section_profile") {
|
if (intent === "document_type_and_account_section_profile") {
|
||||||
const rowsByMarker = new Map();
|
const rowsByMarker = new Map();
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
|
|
@ -4370,8 +4376,5 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function inferReplyType(responseType) {
|
function inferReplyType(responseType) {
|
||||||
if (responseType === "FACTUAL_LIST" || responseType === "FACTUAL_SUMMARY") {
|
return (0, replyPackaging_1.inferAddressReplyType)(responseType);
|
||||||
return "factual";
|
|
||||||
}
|
|
||||||
return "partial_coverage";
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.joinComposeReplyLines = joinComposeReplyLines;
|
||||||
|
exports.finalizeComposeReplyResult = finalizeComposeReplyResult;
|
||||||
|
exports.inferAddressReplyType = inferAddressReplyType;
|
||||||
|
function joinComposeReplyLines(lines) {
|
||||||
|
return lines.join("\n");
|
||||||
|
}
|
||||||
|
function finalizeComposeReplyResult(result, options = {}) {
|
||||||
|
if (!options.emphasizeNumbers || typeof options.emphasizeNumericTokens !== "function") {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
text: String(result.text ?? "")
|
||||||
|
.split("\n")
|
||||||
|
.map((line) => options.emphasizeNumericTokens(line))
|
||||||
|
.join("\n")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function inferAddressReplyType(responseType) {
|
||||||
|
if (responseType === "FACTUAL_LIST" || responseType === "FACTUAL_SUMMARY") {
|
||||||
|
return "factual";
|
||||||
|
}
|
||||||
|
return "partial_coverage";
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,16 @@ import type {
|
||||||
AddressResultMode
|
AddressResultMode
|
||||||
} from "../../types/addressQuery";
|
} from "../../types/addressQuery";
|
||||||
import { normalizeOrganizationScopeValue } from "../assistantOrganizationMatcher";
|
import { normalizeOrganizationScopeValue } from "../assistantOrganizationMatcher";
|
||||||
|
import {
|
||||||
|
finalizeComposeReplyResult,
|
||||||
|
inferAddressReplyType,
|
||||||
|
joinComposeReplyLines,
|
||||||
|
type ComposeFactualReplyOptions as ComposeFactualReplyOptionsBase,
|
||||||
|
type ComposeReplyResult,
|
||||||
|
type ComposeReplySemantics
|
||||||
|
} from "./replyPackaging";
|
||||||
|
|
||||||
|
export type { ComposeFactualReplyOptions, ComposeReplySemantics } from "./replyPackaging";
|
||||||
|
|
||||||
export interface ComposeStageRow {
|
export interface ComposeStageRow {
|
||||||
period: string | null;
|
period: string | null;
|
||||||
|
|
@ -39,26 +49,7 @@ export interface VatDirectSourceProbeSummary {
|
||||||
errors: string[];
|
errors: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ComposeFactualReplyOptions {
|
type ComposeFactualReplyOptions = ComposeFactualReplyOptionsBase<VatDirectSourceProbeSummary>;
|
||||||
userMessage?: string;
|
|
||||||
itemHint?: string;
|
|
||||||
counterpartyHint?: string;
|
|
||||||
organizationHint?: string;
|
|
||||||
accountHint?: string;
|
|
||||||
periodFrom?: string;
|
|
||||||
periodTo?: string;
|
|
||||||
asOfDate?: string;
|
|
||||||
requestedResultMode?: AddressResultMode;
|
|
||||||
vatDirectSourceProbe?: VatDirectSourceProbeSummary | null;
|
|
||||||
emphasizeNumbers?: boolean;
|
|
||||||
useRubCurrency?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ComposeReplySemantics {
|
|
||||||
result_mode?: AddressResultMode;
|
|
||||||
evidence_strength?: AddressEvidenceStrength;
|
|
||||||
balance_confirmed?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
type PeriodProfileFocus =
|
type PeriodProfileFocus =
|
||||||
| "full_profile"
|
| "full_profile"
|
||||||
|
|
@ -2725,9 +2716,19 @@ export function composeFactualReply(
|
||||||
intent: AddressIntent,
|
intent: AddressIntent,
|
||||||
rows: ComposeStageRow[],
|
rows: ComposeStageRow[],
|
||||||
options: ComposeFactualReplyOptions = {}
|
options: ComposeFactualReplyOptions = {}
|
||||||
): { responseType: AddressResponseType; text: string; semantics?: ComposeReplySemantics } {
|
) {
|
||||||
const applyNumericEmphasis = (line: string): string => (options.emphasizeNumbers ? emphasizeNumericTokens(line) : line);
|
return finalizeComposeReplyResult(composeFactualReplyBody(intent, rows, options), {
|
||||||
const joinLines = (lines: string[]): string => lines.map(applyNumericEmphasis).join("\n");
|
emphasizeNumbers: options.emphasizeNumbers,
|
||||||
|
emphasizeNumericTokens
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function composeFactualReplyBody(
|
||||||
|
intent: AddressIntent,
|
||||||
|
rows: ComposeStageRow[],
|
||||||
|
options: ComposeFactualReplyOptions = {}
|
||||||
|
): ComposeReplyResult {
|
||||||
|
const joinLines = joinComposeReplyLines;
|
||||||
|
|
||||||
if (intent === "document_type_and_account_section_profile") {
|
if (intent === "document_type_and_account_section_profile") {
|
||||||
const rowsByMarker = new Map<string, ComposeStageRow[]>();
|
const rowsByMarker = new Map<string, ComposeStageRow[]>();
|
||||||
|
|
@ -5496,8 +5497,5 @@ export function composeFactualReply(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function inferReplyType(responseType: AddressResponseType): "factual" | "partial_coverage" {
|
export function inferReplyType(responseType: AddressResponseType): "factual" | "partial_coverage" {
|
||||||
if (responseType === "FACTUAL_LIST" || responseType === "FACTUAL_SUMMARY") {
|
return inferAddressReplyType(responseType);
|
||||||
return "factual";
|
|
||||||
}
|
|
||||||
return "partial_coverage";
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
import type { AddressEvidenceStrength, AddressResponseType, AddressResultMode } from "../../types/addressQuery";
|
||||||
|
|
||||||
|
export interface ComposeFactualReplyOptions<TVatDirectSourceProbe = unknown> {
|
||||||
|
userMessage?: string;
|
||||||
|
itemHint?: string;
|
||||||
|
counterpartyHint?: string;
|
||||||
|
organizationHint?: string;
|
||||||
|
accountHint?: string;
|
||||||
|
periodFrom?: string;
|
||||||
|
periodTo?: string;
|
||||||
|
asOfDate?: string;
|
||||||
|
requestedResultMode?: AddressResultMode;
|
||||||
|
vatDirectSourceProbe?: TVatDirectSourceProbe | null;
|
||||||
|
emphasizeNumbers?: boolean;
|
||||||
|
useRubCurrency?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ComposeReplySemantics {
|
||||||
|
result_mode?: AddressResultMode;
|
||||||
|
evidence_strength?: AddressEvidenceStrength;
|
||||||
|
balance_confirmed?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ComposeReplyResult {
|
||||||
|
responseType: AddressResponseType;
|
||||||
|
text: string;
|
||||||
|
semantics?: ComposeReplySemantics;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FinalizeComposeReplyOptions {
|
||||||
|
emphasizeNumbers?: boolean;
|
||||||
|
emphasizeNumericTokens?: (line: string) => string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function joinComposeReplyLines(lines: string[]): string {
|
||||||
|
return lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function finalizeComposeReplyResult(
|
||||||
|
result: ComposeReplyResult,
|
||||||
|
options: FinalizeComposeReplyOptions = {}
|
||||||
|
): ComposeReplyResult {
|
||||||
|
if (!options.emphasizeNumbers || typeof options.emphasizeNumericTokens !== "function") {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
text: String(result.text ?? "")
|
||||||
|
.split("\n")
|
||||||
|
.map((line) => options.emphasizeNumericTokens!(line))
|
||||||
|
.join("\n")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function inferAddressReplyType(responseType: AddressResponseType): "factual" | "partial_coverage" {
|
||||||
|
if (responseType === "FACTUAL_LIST" || responseType === "FACTUAL_SUMMARY") {
|
||||||
|
return "factual";
|
||||||
|
}
|
||||||
|
return "partial_coverage";
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import {
|
||||||
|
finalizeComposeReplyResult,
|
||||||
|
inferAddressReplyType,
|
||||||
|
joinComposeReplyLines
|
||||||
|
} from "../src/services/address_runtime/replyPackaging";
|
||||||
|
|
||||||
|
describe("replyPackaging", () => {
|
||||||
|
it("joins reply lines without changing their order", () => {
|
||||||
|
expect(joinComposeReplyLines(["Первая строка", "Вторая строка"])).toBe("Первая строка\nВторая строка");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("applies numeric emphasis line-by-line only when requested", () => {
|
||||||
|
const result = finalizeComposeReplyResult(
|
||||||
|
{
|
||||||
|
responseType: "FACTUAL_SUMMARY",
|
||||||
|
text: "Сумма 1500\nОстаток 22",
|
||||||
|
semantics: { result_mode: "aggregate" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
emphasizeNumbers: true,
|
||||||
|
emphasizeNumericTokens: (line) => line.replace(/\d+/g, (match) => `**${match}**`)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.text).toBe("Сумма **1500**\nОстаток **22**");
|
||||||
|
expect(result.semantics).toEqual({ result_mode: "aggregate" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps reply text unchanged when emphasis is disabled", () => {
|
||||||
|
const result = finalizeComposeReplyResult(
|
||||||
|
{
|
||||||
|
responseType: "FACTUAL_SUMMARY",
|
||||||
|
text: "Сумма 1500"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
emphasizeNumbers: false,
|
||||||
|
emphasizeNumericTokens: () => "не должно примениться"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.text).toBe("Сумма 1500");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("maps factual response types away from partial coverage", () => {
|
||||||
|
expect(inferAddressReplyType("FACTUAL_LIST")).toBe("factual");
|
||||||
|
expect(inferAddressReplyType("FACTUAL_SUMMARY")).toBe("factual");
|
||||||
|
expect(inferAddressReplyType("SOFT_REFUSAL")).toBe("partial_coverage");
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue