import { FEATURE_ASSISTANT_EVIDENCE_ENRICHMENT_V1 } from "../config"; import type { AnswerGroundingCheck, RequirementCoverageReport, UnifiedRetrievalResult } from "../types/assistant"; import { ANSWER_STRUCTURE_SCHEMA_VERSION, type AnswerStructureV11, type EvidenceLimitationReasonCode } from "../types/stage1Contracts"; export interface BuildAssistantAnswerStructureV11Input { assistantReply: string; coverageReport: RequirementCoverageReport; groundingCheck: AnswerGroundingCheck; retrievalResults: UnifiedRetrievalResult[]; options?: { enableEvidenceEnrichment?: boolean; }; } const EVIDENCE_LIMITATION_REASON_CODE_SET: ReadonlySet = new Set([ "snapshot_only", "heuristic_inference", "missing_mechanism", "weak_source_mapping", "insufficient_detail", "unknown" ]); function summarizeUnique(values: Array, limit = 6): string[] { return Array.from(new Set(values.map((item) => String(item ?? "").trim()).filter(Boolean))).slice(0, limit); } function isEvidenceLimitationReasonCode(value: string): value is EvidenceLimitationReasonCode { return EVIDENCE_LIMITATION_REASON_CODE_SET.has(value as EvidenceLimitationReasonCode); } function firstNonEmptyLine(text: string): string { const line = String(text ?? "") .split("\n") .map((item) => item.trim()) .find((item) => item.length > 0); return (line ?? String(text ?? "")).slice(0, 220); } function buildClaimEvidenceLinks( retrievalResults: UnifiedRetrievalResult[] ): NonNullable { const byClaim = new Map(); for (const result of retrievalResults) { for (const evidence of result.evidence) { const claimRef = String(evidence.claim_ref ?? "").trim(); if (!claimRef) { continue; } const evidenceId = String(evidence.evidence_id ?? "").trim(); if (!evidenceId) { continue; } const current = byClaim.get(claimRef) ?? []; current.push(evidenceId); byClaim.set(claimRef, current); } } return Array.from(byClaim.entries()) .slice(0, 10) .map(([claimRef, evidenceIds]) => ({ claim_ref: claimRef, evidence_ids: summarizeUnique(evidenceIds, 10) })); } export function buildAssistantAnswerStructureV11(input: BuildAssistantAnswerStructureV11Input): AnswerStructureV11 { const evidenceIds = summarizeUnique( input.retrievalResults.flatMap((item) => item.evidence.map((evidence) => evidence.evidence_id)), 10 ); const mechanismNotes = summarizeUnique( input.retrievalResults.flatMap((item) => item.evidence .map((evidence) => evidence.mechanism_note) .filter((note): note is string => typeof note === "string" && note.trim().length > 0) ), 6 ); const sourceRefs = summarizeUnique( input.retrievalResults.flatMap((item) => item.evidence .map((evidence) => evidence.source_ref?.canonical_ref) .filter((value): value is string => typeof value === "string" && value.trim().length > 0) ), 8 ); const limitationReasonCodes: EvidenceLimitationReasonCode[] = summarizeUnique( input.retrievalResults.flatMap((item) => item.evidence.flatMap((evidence) => { const code = evidence.limitation?.reason_code; return typeof code === "string" && code.trim().length > 0 ? [code] : []; }) ), 8 ).filter(isEvidenceLimitationReasonCode); const claimEvidenceLinks = buildClaimEvidenceLinks(input.retrievalResults); const limitations = summarizeUnique( [...input.retrievalResults.flatMap((item) => item.limitations), ...input.groundingCheck.reasons], 8 ); const clarificationQuestions = input.coverageReport.clarification_needed_for.map( (item) => `Уточните требование ${item}.` ); const recommendedActions = summarizeUnique( [ ...input.coverageReport.requirements_uncovered.map((item) => `Проверить непокрытое требование ${item}.`), ...input.coverageReport.requirements_partially_covered.map( (item) => `Доуточнить частично покрытое требование ${item}.` ) ], 6 ); const mechanismStatus: AnswerStructureV11["mechanism_block"]["status"] = mechanismNotes.length === 0 ? "unresolved" : limitationReasonCodes.includes("missing_mechanism") || limitationReasonCodes.includes("heuristic_inference") ? "limited" : "grounded"; const enableEvidenceEnrichment = input.options?.enableEvidenceEnrichment ?? FEATURE_ASSISTANT_EVIDENCE_ENRICHMENT_V1; return { schema_version: ANSWER_STRUCTURE_SCHEMA_VERSION, answer_summary: firstNonEmptyLine(input.assistantReply), direct_answer: input.assistantReply, mechanism_block: { status: mechanismStatus, mechanism_notes: mechanismNotes, limitation_reason_codes: limitationReasonCodes }, evidence_block: { evidence_ids: evidenceIds, source_refs: sourceRefs, mechanism_notes: mechanismNotes, coverage_note: input.coverageReport.requirements_total === input.coverageReport.requirements_covered ? "coverage_full_or_near_full" : "coverage_partial_or_limited", ...(enableEvidenceEnrichment && claimEvidenceLinks.length > 0 ? { claim_evidence_links: claimEvidenceLinks } : {}) }, uncertainty_block: { open_uncertainties: input.groundingCheck.missing_requirements, limitations }, next_step_block: { recommended_actions: recommendedActions, clarification_questions: clarificationQuestions } }; }