Stage 3 Wave 1: lifecycle-слой встроен в pipeline

This commit is contained in:
2026-03-26 15:48:38 +03:00
parent 96353cfd48
commit d0b842adb0
59 changed files with 5132 additions and 154 deletions
@@ -10,7 +10,7 @@ import type { RouteHintSummary } from "../types/normalizer";
import type { AnswerStructureV11, EvidenceConfidence, EvidenceItem, EvidenceLimitationReasonCode } from "../types/stage1Contracts";
import type { ProblemUnit, ProblemUnitSummary, ProblemUnitType } from "../types/stage2ProblemUnits";
type ProblemAnswerMode = "stage1_policy_v11" | "stage2_problem_centric_v1";
type ProblemAnswerMode = "stage1_policy_v11" | "stage2_problem_centric_v1" | "stage3_lifecycle_aware_v1";
interface ComposeAnswerInput {
userMessage: string;
@@ -21,6 +21,7 @@ interface ComposeAnswerInput {
groundingCheck: AnswerGroundingCheck;
enableAnswerPolicyV11?: boolean;
enableProblemCentricAnswerV1?: boolean;
enableLifecycleAnswerV1?: boolean;
}
interface ComposeAnswerOutput {
@@ -382,6 +383,61 @@ function formatAffectedScope(unit: ProblemUnit): string {
return scopeParts.join("; ");
}
function formatLifecycleScope(unit: ProblemUnit): string | null {
if (!unit.lifecycle_domain) {
return null;
}
const parts: string[] = [`domain=${unit.lifecycle_domain}`];
if (unit.current_lifecycle_state) {
parts.push(`current=${unit.current_lifecycle_state}`);
}
if (unit.expected_lifecycle_state) {
parts.push(`expected=${unit.expected_lifecycle_state}`);
}
if (unit.lifecycle_defect_type) {
parts.push(`defect=${unit.lifecycle_defect_type}`);
}
if (unit.missing_transition) {
parts.push(`missing_transition=${unit.missing_transition}`);
}
if (unit.invalid_transition) {
parts.push(`invalid_transition=${unit.invalid_transition}`);
}
if (unit.stale_duration) {
parts.push(`stale_duration=${unit.stale_duration}`);
}
return parts.join(", ");
}
function rankProblemUnitsForAnswer(units: ProblemUnit[], lifecycleAnswerEnabled: boolean): ProblemUnit[] {
if (!lifecycleAnswerEnabled) {
return units.slice().sort((left, right) => {
const severityDiff = right.severity.score - left.severity.score;
if (severityDiff !== 0) return severityDiff;
return right.confidence.score - left.confidence.score;
});
}
return units.slice().sort((left, right) => {
const lifecycleRankDiff = (right.lifecycle_ranking_score ?? 0) - (left.lifecycle_ranking_score ?? 0);
if (lifecycleRankDiff !== 0) return lifecycleRankDiff;
const lifecycleConfidenceDiff = (right.lifecycle_confidence?.score ?? 0) - (left.lifecycle_confidence?.score ?? 0);
if (lifecycleConfidenceDiff !== 0) return lifecycleConfidenceDiff;
const severityDiff = right.severity.score - left.severity.score;
if (severityDiff !== 0) return severityDiff;
return right.confidence.score - left.confidence.score;
});
}
function hasLifecycleResolution(units: ProblemUnit[]): boolean {
return units.some(
(unit) =>
Boolean(unit.lifecycle_domain) &&
Boolean(unit.current_lifecycle_state) &&
Boolean(unit.expected_lifecycle_state) &&
Boolean(unit.lifecycle_defect_type)
);
}
function buildProblemCentricActions(input: {
units: ProblemUnit[];
mode: PolicyMode;
@@ -406,6 +462,17 @@ function buildProblemCentricActions(input: {
if (unitTypes.has("lifecycle_anomaly_node")) {
actions.push("Проверьте lifecycle объекта: ожидаемый этап не должен оставаться в partially_linked состоянии.");
}
for (const unit of input.units) {
if (unit.lifecycle_defect_type === "stale_active_state") {
actions.push("Проверьте, почему объект завис: ожидаемый переход не должен оставаться в активной стадии.");
}
if (unit.lifecycle_defect_type === "misclosed_state") {
actions.push("Проверьте закрывающий документ и проводки: закрытие может быть формальным, но некорректным по пути.");
}
if (unit.lifecycle_defect_type === "cross_branch_state_conflict") {
actions.push("Сверьте бухгалтерскую и смежную ветки (например, НДС/расчеты): обнаружен межконтурный конфликт состояния.");
}
}
if (input.mode === "clarification_required") {
if (input.missingAnchors.period) {
@@ -825,7 +892,14 @@ function buildProblemCentricAnswerSummary(input: {
mode: PolicyMode;
weakUnits: boolean;
summary: ProblemUnitSummary | null;
lifecycleEnriched: boolean;
}): string {
if (input.lifecycleEnriched && input.summary?.lifecycle_enriched_units && input.summary.lifecycle_enriched_units > 0) {
if (input.mode === "clarification_required") {
return "Выявлены lifecycle-дефекты, но для надежного вывода требуется уточнение предметных якорей.";
}
return `Сформирован lifecycle-aware problem срез: выделено ${input.summary.lifecycle_enriched_units} lifecycle-узлов с приоритетом по дефектам перехода.`;
}
if (input.mode === "clarification_required") {
return "Выявлены проблемные кластеры, но для надежного вывода требуется предметное уточнение фокуса.";
}
@@ -842,17 +916,31 @@ function buildProblemCentricDirectAnswer(input: {
mode: PolicyMode;
units: ProblemUnit[];
weakUnits: boolean;
lifecycleAnswerEnabled: boolean;
}): string {
const lead =
input.mode === "clarification_required"
? "Обнаружены проблемные зоны, но без уточнения якорей сильный factual-вывод преждевременен."
: input.weakUnits
? "Выделены проблемные зоны с ограниченной надежностью; вывод дан в ограниченном режиме."
: "Выделены ключевые проблемные зоны и их влияние на учетный контур.";
: input.lifecycleAnswerEnabled && hasLifecycleResolution(input.units)
? "Выделены lifecycle-проблемы: определены текущие/ожидаемые стадии и тип нарушения перехода."
: "Выделены ключевые проблемные зоны и их влияние на учетный контур.";
const unitLines = input.units.map((unit) => {
const scope = formatAffectedScope(unit);
return `- ${unit.title}: ${unit.business_defect_class}; ${scope}; severity=${unit.severity.grade}, confidence=${unit.confidence.grade}.`;
const lifecycleScope = input.lifecycleAnswerEnabled ? formatLifecycleScope(unit) : null;
const lifecycleInterpretation = input.lifecycleAnswerEnabled ? unit.business_lifecycle_interpretation : null;
const segments = [
`${unit.title}: ${unit.business_defect_class}`,
scope,
lifecycleScope,
lifecycleInterpretation,
`severity=${unit.severity.grade}`,
`confidence=${unit.confidence.grade}`,
unit.lifecycle_confidence ? `lifecycle_confidence=${unit.lifecycle_confidence.grade}` : null
].filter((item): item is string => Boolean(item));
return `- ${segments.join("; ")}.`;
});
if (unitLines.length === 0) {
@@ -873,8 +961,10 @@ function buildProblemCentricAnswerStructure(input: {
retrievalResults: UnifiedRetrievalResult[];
missingAnchors: MissingAnchors;
coverageReport: RequirementCoverageReport;
lifecycleAnswerEnabled: boolean;
}): AnswerStructureV11 {
const weakUnits = input.selectedUnits.every((item) => item.confidence.grade === "low");
const lifecycleEnriched = input.lifecycleAnswerEnabled && hasLifecycleResolution(input.selectedUnits);
const unitMechanismNotes = uniqueStrings(
input.selectedUnits
.map((item) => item.mechanism_summary)
@@ -932,12 +1022,14 @@ function buildProblemCentricAnswerStructure(input: {
answer_summary: buildProblemCentricAnswerSummary({
mode: input.mode,
weakUnits,
summary: input.problemSummary
summary: input.problemSummary,
lifecycleEnriched
}),
direct_answer: buildProblemCentricDirectAnswer({
mode: input.mode,
units: input.selectedUnits,
weakUnits
weakUnits,
lifecycleAnswerEnabled: input.lifecycleAnswerEnabled
}),
mechanism_block: {
status: mechanismStatus,
@@ -1059,10 +1151,11 @@ function composeAssistantAnswerV11(input: ComposeAnswerInput): ComposeAnswerOutp
.filter((item): item is string => typeof item === "string" && item.trim().length > 0),
6
);
const lifecycleAnswerEnabled = Boolean(input.enableLifecycleAnswerV1);
const problemUnits = flattenProblemUnits(input.retrievalResults);
const problemUnitSummary = selectProblemUnitSummary(input.retrievalResults);
const problemHeavyUnits = problemUnits.filter((item) => PROBLEM_HEAVY_TYPES.has(item.problem_unit_type));
const selectedProblemUnits = problemHeavyUnits.slice(0, 4);
const selectedProblemUnits = rankProblemUnitsForAnswer(problemHeavyUnits, lifecycleAnswerEnabled).slice(0, 4);
const claimEvidenceLinks = buildClaimEvidenceLinks(input.retrievalResults);
const aggregateEvidenceConfidence = aggregateConfidence(input.retrievalResults, evidenceItems);
const lowConfidenceSignals = evidenceItems.filter((item) => item.confidence === "low").length;
@@ -1138,9 +1231,12 @@ function composeAssistantAnswerV11(input: ComposeAnswerInput): ComposeAnswerOutp
groundingCheck: input.groundingCheck,
retrievalResults: input.retrievalResults,
missingAnchors,
coverageReport: input.coverageReport
coverageReport: input.coverageReport,
lifecycleAnswerEnabled
});
const lifecycleModeActive = lifecycleAnswerEnabled && hasLifecycleResolution(selectedProblemUnits);
return {
assistant_reply: renderPolicyReply(problemCentricStructure),
fallback_type: decision.fallback_type,
@@ -1148,7 +1244,7 @@ function composeAssistantAnswerV11(input: ComposeAnswerInput): ComposeAnswerOutp
answer_structure_v11: problemCentricStructure,
problem_centric_answer_applied: true,
problem_units_used_count: selectedProblemUnits.length,
problem_answer_mode: "stage2_problem_centric_v1",
problem_answer_mode: lifecycleModeActive ? "stage3_lifecycle_aware_v1" : "stage2_problem_centric_v1",
problem_unit_ids_used: selectedProblemUnits.map((item) => item.problem_unit_id)
};
}