ARCH: ввести data-need graph и довести open-scope comparison до live replay
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user