ARCH: ввести data-need graph и довести open-scope comparison до live replay

This commit is contained in:
2026-04-22 20:38:36 +03:00
parent dca49ef4e1
commit f2bd2dfdb1
27 changed files with 2832 additions and 43 deletions
@@ -78,6 +78,28 @@ export interface AssistantMcpDiscoveryDerivedValueFlow {
inference_basis: "sum_of_confirmed_1c_value_flow_rows";
}
export interface AssistantMcpDiscoveryRankedValueFlowBucket {
axis_value: string;
rows_with_amount: number;
total_amount: number;
total_amount_human_ru: string;
}
export interface AssistantMcpDiscoveryDerivedRankedValueFlow {
value_flow_direction: "incoming_customer_revenue" | "outgoing_supplier_payout";
ranking_need: "top_desc" | "bottom_asc";
ranking_axis: "counterparty";
organization_scope: string | null;
period_scope: string | null;
rows_matched: number;
rows_with_amount: number;
ranked_values: AssistantMcpDiscoveryRankedValueFlowBucket[];
coverage_limited_by_probe_limit: boolean;
coverage_recovered_by_period_chunking: boolean;
period_chunking_granularity: AssistantMcpDiscoveryAggregationAxis | null;
inference_basis: "ranked_counterparty_totals_from_confirmed_1c_value_flow_rows";
}
export interface AssistantMcpDiscoveryValueFlowSideSummary {
rows_matched: number;
rows_with_amount: number;
@@ -187,6 +209,7 @@ export interface AssistantMcpDiscoveryPilotExecutionContract {
derived_metadata_surface: AssistantMcpDiscoveryDerivedMetadataSurface | null;
derived_entity_resolution: AssistantMcpDiscoveryDerivedEntityResolution | null;
derived_activity_period: AssistantMcpDiscoveryDerivedActivityPeriod | null;
derived_ranked_value_flow?: AssistantMcpDiscoveryDerivedRankedValueFlow | null;
derived_value_flow: AssistantMcpDiscoveryDerivedValueFlow | null;
derived_bidirectional_value_flow: AssistantMcpDiscoveryDerivedBidirectionalValueFlow | null;
query_limitations: string[];
@@ -334,6 +357,20 @@ function buildValueFlowFilters(planner: AssistantMcpDiscoveryPlannerContract): A
};
}
function organizationScopeForPlanner(planner: AssistantMcpDiscoveryPlannerContract): string | null {
return toNonEmptyString(planner.discovery_plan.turn_meaning_ref?.explicit_organization_scope);
}
function rankingNeedForPlanner(
planner: AssistantMcpDiscoveryPlannerContract
): AssistantMcpDiscoveryDerivedRankedValueFlow["ranking_need"] | null {
const rankingNeed = toNonEmptyString(planner.data_need_graph?.ranking_need)?.toLowerCase();
if (rankingNeed === "top_desc" || rankingNeed === "bottom_asc") {
return rankingNeed;
}
return null;
}
function normalizeEntityResolutionText(value: string | null): string {
return String(value ?? "")
.toLowerCase()
@@ -544,7 +581,11 @@ function isMovementEvidencePilotEligible(planner: AssistantMcpDiscoveryPlannerCo
}
function isValueFlowPilotEligible(planner: AssistantMcpDiscoveryPlannerContract): boolean {
if (planner.selected_chain_id === "value_flow") {
if (
planner.selected_chain_id === "value_flow" ||
planner.selected_chain_id === "value_flow_ranking" ||
planner.selected_chain_id === "value_flow_comparison"
) {
return true;
}
const meaning = planner.discovery_plan.turn_meaning_ref;
@@ -1429,6 +1470,17 @@ function rowAmountValue(row: Record<string, unknown>): number | null {
return null;
}
function rowCounterpartyValue(row: Record<string, unknown>): string | null {
const candidates = [row["Контрагент"], row["Counterparty"], row["counterparty"], row["Наименование"], row["name"]];
for (const candidate of candidates) {
const text = toNonEmptyString(candidate);
if (text) {
return text;
}
}
return null;
}
function monthBucketFromIsoDate(isoDate: string | null): string | null {
const match = isoDate?.match(/^(\d{4})-(\d{2})-\d{2}$/);
return match ? `${match[1]}-${match[2]}` : null;
@@ -1629,6 +1681,74 @@ function deriveValueFlow(
};
}
function deriveRankedValueFlow(
result: AssistantMcpDiscoveryCoverageAwareQueryResult | null,
input: {
organizationScope: string | null;
periodScope: string | null;
direction: AssistantMcpDiscoveryDerivedRankedValueFlow["value_flow_direction"];
rankingNeed: AssistantMcpDiscoveryDerivedRankedValueFlow["ranking_need"];
}
): AssistantMcpDiscoveryDerivedRankedValueFlow | null {
if (!result || result.error || result.matched_rows <= 0) {
return null;
}
const buckets = new Map<string, { rows_with_amount: number; total_amount: number }>();
let rowsWithAmount = 0;
for (const row of result.rows) {
const axisValue = rowCounterpartyValue(row);
const amount = rowAmountValue(row);
if (!axisValue || amount === null) {
continue;
}
rowsWithAmount += 1;
const current = buckets.get(axisValue) ?? { rows_with_amount: 0, total_amount: 0 };
current.rows_with_amount += 1;
current.total_amount += amount;
buckets.set(axisValue, current);
}
if (rowsWithAmount <= 0 || buckets.size <= 0) {
return null;
}
const rankedValues = Array.from(buckets.entries())
.map(([axisValue, bucket]) => ({
axis_value: axisValue,
rows_with_amount: bucket.rows_with_amount,
total_amount: bucket.total_amount,
total_amount_human_ru: formatAmountHumanRu(bucket.total_amount)
}))
.sort((left, right) => {
const amountDelta = right.total_amount - left.total_amount;
if (input.rankingNeed === "bottom_asc") {
if (amountDelta !== 0) {
return -amountDelta;
}
} else if (amountDelta !== 0) {
return amountDelta;
}
return left.axis_value.localeCompare(right.axis_value, "ru");
})
.slice(0, 5);
return {
value_flow_direction: input.direction,
ranking_need: input.rankingNeed,
ranking_axis: "counterparty",
organization_scope: input.organizationScope,
period_scope: input.periodScope,
rows_matched: result.matched_rows,
rows_with_amount: rowsWithAmount,
ranked_values: rankedValues,
coverage_limited_by_probe_limit: result.coverage_limited_by_probe_limit,
coverage_recovered_by_period_chunking: result.coverage_recovered_by_period_chunking,
period_chunking_granularity: result.period_chunking_granularity,
inference_basis: "ranked_counterparty_totals_from_confirmed_1c_value_flow_rows"
};
}
function deriveValueFlowSideSummary(
result: AssistantMcpDiscoveryCoverageAwareQueryResult | null
): AssistantMcpDiscoveryValueFlowSideSummary {
@@ -1798,6 +1918,18 @@ function buildValueFlowConfirmedFacts(
];
}
function buildRankedValueFlowConfirmedFacts(derived: AssistantMcpDiscoveryDerivedRankedValueFlow | null): string[] {
if (!derived || derived.ranked_values.length <= 0) {
return [];
}
const leader = derived.ranked_values[0];
const directionLabel =
derived.value_flow_direction === "outgoing_supplier_payout" ? "supplier-payout" : "incoming value-flow";
return [
`1C ${directionLabel} rows were ranked by counterparty for the checked scope; leader=${leader.axis_value}, rows_with_amount=${leader.rows_with_amount}`
];
}
function buildBidirectionalValueFlowConfirmedFacts(
derived: AssistantMcpDiscoveryDerivedBidirectionalValueFlow | null
): string[] {
@@ -1880,6 +2012,19 @@ function buildValueFlowInferredFacts(derived: AssistantMcpDiscoveryDerivedValueF
return facts;
}
function buildRankedValueFlowInferredFacts(derived: AssistantMcpDiscoveryDerivedRankedValueFlow | null): string[] {
if (!derived) {
return [];
}
const facts = ["Counterparty ranking was calculated from confirmed 1C movement rows grouped by counterparty"];
if (derived.coverage_recovered_by_period_chunking && derived.period_chunking_granularity === "month") {
facts.push(
"Requested period coverage for counterparty ranking was recovered through monthly 1C probes after a broad probe hit the row limit"
);
}
return facts;
}
function buildBidirectionalValueFlowInferredFacts(
derived: AssistantMcpDiscoveryDerivedBidirectionalValueFlow | null
): string[] {
@@ -1939,6 +2084,22 @@ function buildValueFlowUnknownFacts(
return unknownFacts;
}
function buildRankedValueFlowUnknownFacts(
periodScope: string | null,
derived: AssistantMcpDiscoveryDerivedRankedValueFlow | null
): string[] {
const unknownFacts: string[] = [];
if (derived?.coverage_limited_by_probe_limit) {
unknownFacts.push("Complete requested-period ranking coverage is not proven because the MCP discovery probe row limit was reached");
}
unknownFacts.push(
periodScope
? "Full ranking outside the checked period is not proven by this MCP discovery pilot"
: "Full all-time counterparty ranking is not proven without an explicit checked period"
);
return unknownFacts;
}
function buildBidirectionalValueFlowUnknownFacts(
periodScope: string | null,
derived: AssistantMcpDiscoveryDerivedBidirectionalValueFlow | null
@@ -1979,6 +2140,8 @@ function pilotScopeForPlanner(planner: AssistantMcpDiscoveryPlannerContract): As
return "metadata_inspection_v1";
case "movement_evidence":
return "counterparty_movement_evidence_query_movements_v1";
case "value_flow_comparison":
case "value_flow_ranking":
case "value_flow":
return valueFlowPilotProfile(planner).scope;
case "document_evidence":
@@ -2107,7 +2270,9 @@ export async function executeAssistantMcpDiscoveryPilot(
const counterparty = firstEntityCandidate(planner);
const dateScope = toNonEmptyString(planner.discovery_plan.turn_meaning_ref?.explicit_date_scope);
const organizationScope = organizationScopeForPlanner(planner);
const aggregationAxis = aggregationAxisForPlanner(planner);
const rankingNeed = rankingNeedForPlanner(planner);
if (metadataPilotEligible) {
let metadataResult: AddressMcpMetadataRowsResult | null = null;
@@ -2694,6 +2859,50 @@ export async function executeAssistantMcpDiscoveryPilot(
}
const sourceRowsSummary = queryResult ? summarizeValueFlowRows(queryResult) : null;
if (planner.selected_chain_id === "value_flow_ranking" && rankingNeed) {
const derivedRankedValueFlow = deriveRankedValueFlow(queryResult, {
organizationScope,
periodScope: dateScope,
direction: valueFlowProfile.direction,
rankingNeed
});
if (derivedRankedValueFlow) {
pushReason(reasonCodes, "pilot_derived_ranked_value_flow_from_confirmed_rows");
}
const evidence = resolveAssistantMcpDiscoveryEvidence({
plan: planner.discovery_plan,
probeResults,
confirmedFacts: buildRankedValueFlowConfirmedFacts(derivedRankedValueFlow),
inferredFacts: buildRankedValueFlowInferredFacts(derivedRankedValueFlow),
unknownFacts: buildRankedValueFlowUnknownFacts(dateScope, derivedRankedValueFlow),
sourceRowsSummary,
queryLimitations,
recommendedNextProbe: "explain_evidence_basis"
});
return {
schema_version: ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
policy_owner: "assistantMcpDiscoveryPilotExecutor",
pilot_status: "executed",
pilot_scope: valueFlowProfile.scope,
dry_run: dryRun,
mcp_execution_performed: executedPrimitives.length > 0,
executed_primitives: executedPrimitives,
skipped_primitives: skippedPrimitives,
probe_results: probeResults,
evidence,
source_rows_summary: sourceRowsSummary,
derived_metadata_surface: null,
derived_entity_resolution: null,
derived_activity_period: null,
derived_ranked_value_flow: derivedRankedValueFlow,
derived_value_flow: null,
derived_bidirectional_value_flow: null,
query_limitations: queryLimitations,
reason_codes: reasonCodes
};
}
const derivedValueFlow = deriveValueFlow(
queryResult,
counterparty,
@@ -2733,6 +2942,7 @@ export async function executeAssistantMcpDiscoveryPilot(
derived_metadata_surface: null,
derived_entity_resolution: null,
derived_activity_period: null,
derived_ranked_value_flow: null,
derived_value_flow: derivedValueFlow,
derived_bidirectional_value_flow: null,
query_limitations: queryLimitations,