ARCH: добавить вывод длительности активности MCP discovery

This commit is contained in:
2026-04-20 12:35:29 +03:00
parent 72804557aa
commit e85d456576
8 changed files with 293 additions and 3 deletions
@@ -130,6 +130,18 @@ function buildMustNotClaim(pilot: AssistantMcpDiscoveryPilotExecutionContract):
return claims;
}
function derivedActivityInferenceLine(pilot: AssistantMcpDiscoveryPilotExecutionContract): string | null {
const period = pilot.derived_activity_period;
if (!period) {
return null;
}
return [
`По подтвержденным строкам активности в 1С период взаимодействия можно оценить примерно как ${period.duration_human_ru}.`,
`Первая найденная активность: ${period.first_activity_date}; последняя найденная активность: ${period.latest_activity_date}.`,
"Это вывод по данным 1С, а не юридически подтвержденный возраст регистрации."
].join(" ");
}
export function buildAssistantMcpDiscoveryAnswerDraft(
pilot: AssistantMcpDiscoveryPilotExecutionContract
): AssistantMcpDiscoveryAnswerDraftContract {
@@ -142,6 +154,10 @@ export function buildAssistantMcpDiscoveryAnswerDraft(
if (pilot.evidence.inferred_facts.length > 0) {
pushReason(reasonCodes, "answer_contains_bounded_inference");
}
const derivedInferenceLine = derivedActivityInferenceLine(pilot);
const inferenceLines = derivedInferenceLine
? [derivedInferenceLine]
: pilot.evidence.inferred_facts;
return {
schema_version: ASSISTANT_MCP_DISCOVERY_ANSWER_DRAFT_SCHEMA_VERSION,
@@ -149,7 +165,7 @@ export function buildAssistantMcpDiscoveryAnswerDraft(
answer_mode: mode,
headline: headlineFor(mode),
confirmed_lines: uniqueStrings(pilot.evidence.confirmed_facts),
inference_lines: uniqueStrings(pilot.evidence.inferred_facts),
inference_lines: uniqueStrings(inferenceLines),
unknown_lines: uniqueStrings(pilot.evidence.unknown_facts),
limitation_lines: userFacingLimitations([...pilot.query_limitations, ...pilot.evidence.query_limitations]),
next_step_line: nextStepFor(mode, pilot),
@@ -29,6 +29,17 @@ export interface AssistantMcpDiscoveryPilotExecutorDeps {
executeAddressMcpQuery: typeof executeAddressMcpQuery;
}
export interface AssistantMcpDiscoveryDerivedActivityPeriod {
first_activity_date: string;
latest_activity_date: string;
matched_rows: number;
duration_total_months: number;
duration_years: number;
duration_months_remainder: number;
duration_human_ru: string;
inference_basis: "first_and_latest_confirmed_1c_activity_rows";
}
export interface AssistantMcpDiscoveryPilotExecutionContract {
schema_version: typeof ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION;
policy_owner: "assistantMcpDiscoveryPilotExecutor";
@@ -41,6 +52,7 @@ export interface AssistantMcpDiscoveryPilotExecutionContract {
probe_results: AssistantMcpDiscoveryProbeResult[];
evidence: AssistantMcpDiscoveryEvidenceContract;
source_rows_summary: string | null;
derived_activity_period: AssistantMcpDiscoveryDerivedActivityPeriod | null;
query_limitations: string[];
reason_codes: string[];
}
@@ -173,6 +185,80 @@ function summarizeRows(result: AddressMcpQueryExecutorResult): string | null {
return `${result.fetched_rows} MCP document rows fetched, ${result.matched_rows} matched lifecycle scope`;
}
function rowDateValue(row: Record<string, unknown>): string | null {
const candidates = [
row["Период"],
row["Period"],
row["period"],
row["Дата"],
row["Date"],
row["date"]
];
for (const candidate of candidates) {
const text = toNonEmptyString(candidate);
const match = text?.match(/(\d{4})-(\d{2})-(\d{2})/);
if (match) {
return `${match[1]}-${match[2]}-${match[3]}`;
}
}
return null;
}
function monthDiff(firstIsoDate: string, latestIsoDate: string): number {
const first = new Date(`${firstIsoDate}T00:00:00.000Z`);
const latest = new Date(`${latestIsoDate}T00:00:00.000Z`);
if (Number.isNaN(first.getTime()) || Number.isNaN(latest.getTime()) || latest < first) {
return 0;
}
let months = (latest.getUTCFullYear() - first.getUTCFullYear()) * 12;
months += latest.getUTCMonth() - first.getUTCMonth();
if (latest.getUTCDate() < first.getUTCDate()) {
months -= 1;
}
return Math.max(0, months);
}
function formatDurationHumanRu(totalMonths: number): string {
const years = Math.floor(totalMonths / 12);
const months = totalMonths % 12;
const parts: string[] = [];
if (years > 0) {
parts.push(`${years} ${years === 1 ? "год" : years >= 2 && years <= 4 ? "года" : "лет"}`);
}
if (months > 0) {
parts.push(`${months} ${months === 1 ? "месяц" : months >= 2 && months <= 4 ? "месяца" : "месяцев"}`);
}
return parts.length > 0 ? parts.join(" ") : "меньше месяца";
}
function deriveActivityPeriod(
result: AddressMcpQueryExecutorResult | null
): AssistantMcpDiscoveryDerivedActivityPeriod | null {
if (!result || result.error || result.matched_rows <= 0) {
return null;
}
const dates = result.rows
.map((row) => rowDateValue(row))
.filter((value): value is string => Boolean(value))
.sort();
if (dates.length === 0) {
return null;
}
const first = dates[0];
const latest = dates[dates.length - 1];
const totalMonths = monthDiff(first, latest);
return {
first_activity_date: first,
latest_activity_date: latest,
matched_rows: result.matched_rows,
duration_total_months: totalMonths,
duration_years: Math.floor(totalMonths / 12),
duration_months_remainder: totalMonths % 12,
duration_human_ru: formatDurationHumanRu(totalMonths),
inference_basis: "first_and_latest_confirmed_1c_activity_rows"
};
}
function buildConfirmedFacts(result: AddressMcpQueryExecutorResult, counterparty: string | null): string[] {
if (result.error || result.matched_rows <= 0) {
return [];
@@ -236,6 +322,7 @@ export async function executeAssistantMcpDiscoveryPilot(
probe_results: probeResults,
evidence,
source_rows_summary: null,
derived_activity_period: null,
query_limitations: ["MCP discovery pilot was blocked before execution"],
reason_codes: reasonCodes
};
@@ -256,6 +343,7 @@ export async function executeAssistantMcpDiscoveryPilot(
probe_results: probeResults,
evidence,
source_rows_summary: null,
derived_activity_period: null,
query_limitations: ["MCP discovery pilot needs more scope before execution"],
reason_codes: reasonCodes
};
@@ -280,6 +368,7 @@ export async function executeAssistantMcpDiscoveryPilot(
probe_results: probeResults,
evidence,
source_rows_summary: null,
derived_activity_period: null,
query_limitations: ["MCP discovery pilot scope is not implemented yet"],
reason_codes: reasonCodes
};
@@ -304,6 +393,7 @@ export async function executeAssistantMcpDiscoveryPilot(
probe_results: probeResults,
evidence,
source_rows_summary: null,
derived_activity_period: null,
query_limitations: ["Lifecycle recipe is not available"],
reason_codes: reasonCodes
};
@@ -332,6 +422,10 @@ export async function executeAssistantMcpDiscoveryPilot(
}
const sourceRowsSummary = queryResult ? summarizeRows(queryResult) : null;
const derivedActivityPeriod = deriveActivityPeriod(queryResult);
if (derivedActivityPeriod) {
pushReason(reasonCodes, "pilot_derived_activity_period_from_confirmed_rows");
}
const evidence = resolveAssistantMcpDiscoveryEvidence({
plan: planner.discovery_plan,
probeResults,
@@ -355,6 +449,7 @@ export async function executeAssistantMcpDiscoveryPilot(
probe_results: probeResults,
evidence,
source_rows_summary: sourceRowsSummary,
derived_activity_period: derivedActivityPeriod,
query_limitations: queryLimitations,
reason_codes: reasonCodes
};