import type { RetrievalConfidence, RetrievalResultStatus, RetrievalResultType, UnifiedRetrievalResult } from "../types/assistant"; import { FEATURE_ASSISTANT_EVIDENCE_ENRICHMENT_V1, FEATURE_ASSISTANT_GRAPH_RUNTIME_V1, FEATURE_ASSISTANT_PROBLEM_UNITS_V1 } from "../config"; import { EVIDENCE_SOURCE_REF_SCHEMA_VERSION } from "../types/stage1Contracts"; import type { EvidenceConfidence, EvidenceItem, EvidenceLimitationReasonCode, EvidenceKind, EvidencePointer, EvidenceSourceRef } from "../types/stage1Contracts"; import { assembleProblemUnits } from "./problemUnitAssembler"; import { buildAccountingGraph } from "./stage4GraphRuntime"; import type { GraphSignalSummary } from "../types/stage4Graph"; interface RawRetrievalResult { status?: string; result_type?: string; items?: unknown; summary?: unknown; evidence?: unknown; why_included?: unknown; selection_reason?: unknown; risk_factors?: unknown; business_interpretation?: unknown; confidence?: unknown; limitations?: unknown; errors?: unknown; } function toObject(value: unknown): Record | null { if (!value || typeof value !== "object" || Array.isArray(value)) { return null; } return value as Record; } function normalizeRawRetrievalResult(raw: unknown): RawRetrievalResult { const source = toObject(raw); if (!source) { return {}; } return { status: source.status as string | undefined, result_type: source.result_type as string | undefined, items: source.items, summary: source.summary, evidence: source.evidence, why_included: source.why_included, selection_reason: source.selection_reason, risk_factors: source.risk_factors, business_interpretation: source.business_interpretation, confidence: source.confidence, limitations: source.limitations, errors: source.errors }; } function toStringOrNull(value: unknown): string | null { if (typeof value !== "string") return null; const trimmed = value.trim(); return trimmed.length > 0 ? trimmed : null; } function toNumberOrNull(value: unknown): number | null { if (typeof value === "number" && Number.isFinite(value)) { return value; } return null; } function normalizeStatus(value: string | undefined): RetrievalResultStatus { if (value === "ok" || value === "empty" || value === "partial" || value === "error") { return value; } return "error"; } function normalizeResultType(value: string | undefined): RetrievalResultType { if (value === "list" || value === "summary" || value === "object" || value === "chain" || value === "ranking") { return value; } return "summary"; } function normalizeObjectArray(value: unknown): Array> { if (!Array.isArray(value)) { return []; } return value .map((item) => (item && typeof item === "object" ? (item as Record) : null)) .filter((item): item is Record => item !== null); } function normalizeSummary(value: unknown): Record { if (!value || typeof value !== "object" || Array.isArray(value)) { return {}; } return value as Record; } function normalizeErrors(value: unknown): string[] { if (!Array.isArray(value)) { return []; } return value.map((item) => String(item)); } function normalizeStringArray(value: unknown): string[] { if (!Array.isArray(value)) { return []; } return value.map((item) => String(item)); } function normalizeNumberRecord(value: unknown): Record { const source = toObject(value); if (!source) { return {}; } const result: Record = {}; for (const [key, entry] of Object.entries(source)) { if (typeof entry === "number" && Number.isFinite(entry)) { result[key] = entry; } } return result; } function mergeSummaryWithProblemUnitMeta( summary: Record, input: { candidateEvidenceCount: number; problemUnitsCount: number; unitTypes: string[]; duplicateCollapses: number; severityDistribution: Record; confidenceDistribution: Record; lifecycleEnrichedUnits: number; lifecycleDomainDistribution: Record; lifecycleDefectDistribution: Record; } ): Record { return { ...summary, problem_units_enabled: true, candidate_evidence_count: input.candidateEvidenceCount, problem_units_count: input.problemUnitsCount, problem_unit_types: input.unitTypes, problem_unit_duplicate_collapses: input.duplicateCollapses, problem_unit_severity_distribution: input.severityDistribution, problem_unit_confidence_distribution: input.confidenceDistribution, lifecycle_enriched_units: input.lifecycleEnrichedUnits, problem_unit_lifecycle_domain_distribution: input.lifecycleDomainDistribution, problem_unit_lifecycle_defect_distribution: input.lifecycleDefectDistribution }; } function mergeSummaryWithGraphMeta(summary: Record, graphSummary: GraphSignalSummary): Record { return { ...summary, graph_runtime_enabled: true, graph_total_units: graphSummary.total_units, graph_bound_units: graphSummary.bound_units, graph_nodes_count: graphSummary.node_count, graph_edges_count: graphSummary.edge_count, graph_missing_links_count: graphSummary.missing_links_count, graph_conflicting_links_count: graphSummary.conflicting_links_count, graph_coverage_grade: graphSummary.graph_coverage_grade, graph_domain_distribution: graphSummary.domain_distribution, graph_relation_distribution: graphSummary.relation_distribution }; } function normalizeConfidence(value: unknown): RetrievalConfidence { if (value === "high" || value === "medium" || value === "low") { return value; } return "medium"; } function parseEvidenceConfidence(value: unknown): EvidenceConfidence | null { if (value === "high" || value === "medium" || value === "low") { return value; } return null; } function normalizeEvidenceNamespace(value: unknown): EvidencePointer["source"]["namespace"] { const normalized = toStringOrNull(value)?.toLowerCase(); if (!normalized) return "unknown"; if (normalized === "snapshot_2020" || normalized === "snapshot") return "snapshot_2020"; if (normalized === "assistant_derived" || normalized === "derived") return "assistant_derived"; return "unknown"; } function inferEvidenceKind(item: Record): EvidenceKind { if (item.mechanism_of_failure !== undefined || item.failed_expected_edge !== undefined || item.expected_next_step !== undefined) { return "mechanism_link"; } if (item.risk_score !== undefined || item.zero_guid_values !== undefined || item.unknown_link_count !== undefined) { return "anomaly_signal"; } if (item.records_count !== undefined || item.operations_count !== undefined || item.document_refs_count !== undefined) { return "aggregation"; } if (item.limitation !== undefined || item.is_snapshot_limited !== undefined) { return "limitation_note"; } return "factual_anchor"; } function inferMechanismNoteLegacy(kind: EvidenceKind, item: Record): string { const explicit = toStringOrNull(item.mechanism_note); if (explicit) { return explicit; } if (kind === "mechanism_link") { const failure = toStringOrNull(item.mechanism_of_failure); if (failure) return failure; return "Mechanism link inferred from retrieval evidence."; } if (kind === "anomaly_signal") { return "Anomaly signal inferred from risk-oriented fields."; } if (kind === "aggregation") { return "Aggregated evidence item."; } if (kind === "limitation_note") { return "Evidence includes explicit limitation hints."; } return "Factual evidence anchor."; } interface MechanismNoteResolution { note: string | null; reliable: boolean; } function resolveMechanismNote(kind: EvidenceKind, item: Record): MechanismNoteResolution { const explicit = toStringOrNull(item.mechanism_note); if (explicit) { return { note: explicit, reliable: true }; } if (kind === "mechanism_link") { const failure = toStringOrNull(item.mechanism_of_failure); if (failure) { return { note: failure, reliable: true }; } const failedEdge = toStringOrNull(item.failed_expected_edge); const expectedNext = toStringOrNull(item.expected_next_step); const composed = [failedEdge ? `failed_edge=${failedEdge}` : null, expectedNext ? `expected_next_step=${expectedNext}` : null] .filter((part): part is string => Boolean(part)) .join("; "); if (composed) { return { note: composed, reliable: true }; } } if (!FEATURE_ASSISTANT_EVIDENCE_ENRICHMENT_V1) { return { note: inferMechanismNoteLegacy(kind, item), reliable: false }; } return { note: null, reliable: false }; } function normalizeEvidenceSourceType(value: unknown, record: Record): EvidenceItem["source_type"] { const normalized = toStringOrNull(value); if (normalized === "retrieval_item" || normalized === "retrieval_summary" || normalized === "derived") { return normalized; } if (record.records_count !== undefined || record.operations_count !== undefined || record.document_refs_count !== undefined) { return "retrieval_summary"; } return "retrieval_item"; } function readPointer(record: Record): Record { const pointer = toObject(record.pointer); return pointer ?? {}; } interface NormalizedPointerResult { pointer: EvidencePointer; fallback_source_namespace: boolean; fallback_source_entity: boolean; fallback_source_id: boolean; } function normalizeEvidencePointer( fragmentId: string, route: string, record: Record, index: number ): NormalizedPointerResult { const pointer = readPointer(record); const source = toObject(pointer.source); const locator = toObject(pointer.locator); const sourceEntityCandidate = toStringOrNull(source?.entity) ?? toStringOrNull(record.source_entity); const sourceEntity = sourceEntityCandidate ?? "unknown_entity"; const sourceIdCandidate = toStringOrNull(source?.id) ?? toStringOrNull(record.source_id); const sourceId = sourceIdCandidate ?? `${route}:${fragmentId}:${index + 1}`; const period = toStringOrNull(source?.period) ?? toStringOrNull(record.period); const namespace = normalizeEvidenceNamespace(source?.namespace ?? record.source_namespace); return { pointer: { fragment_id: toStringOrNull(pointer.fragment_id) ?? fragmentId, route: toStringOrNull(pointer.route) ?? route, source: { namespace, entity: sourceEntity, id: sourceId, period }, locator: { field_path: toStringOrNull(locator?.field_path) ?? toStringOrNull(record.field_path), item_index: toNumberOrNull(locator?.item_index) ?? index } }, fallback_source_namespace: namespace === "unknown", fallback_source_entity: sourceEntityCandidate === null, fallback_source_id: sourceIdCandidate === null }; } function canonicalizeSourceRefPart(value: string | null): string { return encodeURIComponent((value ?? "none").trim().toLowerCase()); } function buildSourceRef(pointer: EvidencePointer): EvidenceSourceRef { return { schema_version: EVIDENCE_SOURCE_REF_SCHEMA_VERSION, namespace: pointer.source.namespace, entity: pointer.source.entity, id: pointer.source.id, period: pointer.source.period, canonical_ref: [ EVIDENCE_SOURCE_REF_SCHEMA_VERSION, canonicalizeSourceRefPart(pointer.source.namespace), canonicalizeSourceRefPart(pointer.source.entity), canonicalizeSourceRefPart(pointer.source.id), canonicalizeSourceRefPart(pointer.source.period) ].join("|") }; } function toBoolean(value: unknown): boolean { if (typeof value === "boolean") return value; if (typeof value === "number") return value !== 0; if (typeof value === "string") { const lowered = value.trim().toLowerCase(); return lowered === "true" || lowered === "1" || lowered === "yes"; } return false; } function limitationCodeFromText(text: string): EvidenceLimitationReasonCode { const lower = text.toLowerCase(); if (/(snapshot|read-only|read only)/i.test(lower)) { return "snapshot_only"; } if (/heuristic/i.test(lower)) { return "heuristic_inference"; } if (/mechanism/i.test(lower)) { return "missing_mechanism"; } if (/(guid|detail|specific)/i.test(lower)) { return "insufficient_detail"; } return "unknown"; } interface LimitationResolutionInput { record: Record; sourceType: EvidenceItem["source_type"]; evidenceKind: EvidenceKind; mechanismReliable: boolean; mechanismExpected: boolean; pointerWeak: boolean; } function resolveEvidenceLimitation(input: LimitationResolutionInput): EvidenceItem["limitation"] { const explicitLimitation = toStringOrNull(input.record.limitation); if (explicitLimitation) { return { reason_code: limitationCodeFromText(explicitLimitation), note: explicitLimitation }; } if (toBoolean(input.record.is_snapshot_limited)) { return { reason_code: "snapshot_only", note: null }; } if (!FEATURE_ASSISTANT_EVIDENCE_ENRICHMENT_V1) { return null; } if (input.mechanismExpected && !input.mechanismReliable) { return { reason_code: "missing_mechanism", note: null }; } if (input.pointerWeak) { return { reason_code: "weak_source_mapping", note: null }; } if (input.sourceType === "derived") { return { reason_code: "heuristic_inference", note: null }; } if (input.evidenceKind === "limitation_note") { return { reason_code: "unknown", note: null }; } return null; } function downgradeConfidence(value: EvidenceConfidence): EvidenceConfidence { if (value === "high") return "medium"; if (value === "medium") return "low"; return "low"; } interface ConfidenceResolutionInput { explicitConfidence: EvidenceConfidence | null; sourceType: EvidenceItem["source_type"]; mechanismReliable: boolean; mechanismExpected: boolean; limitation: EvidenceItem["limitation"]; pointerWeak: boolean; } function resolveEvidenceConfidence(input: ConfidenceResolutionInput): EvidenceConfidence { if (!FEATURE_ASSISTANT_EVIDENCE_ENRICHMENT_V1) { return input.explicitConfidence ?? "medium"; } let confidence: EvidenceConfidence = input.explicitConfidence ?? (input.sourceType === "retrieval_item" ? "medium" : "low"); if (input.limitation?.reason_code === "missing_mechanism" || input.limitation?.reason_code === "weak_source_mapping") { confidence = downgradeConfidence(confidence); } if (input.sourceType === "derived" && !input.explicitConfidence) { confidence = "low"; } if (input.mechanismExpected && !input.mechanismReliable) { confidence = "low"; } if (input.pointerWeak) { confidence = "low"; } return confidence; } function normalizeEvidenceItems( fragmentId: string, requirementIds: string[], route: string, value: unknown ): EvidenceItem[] { const records = normalizeObjectArray(value); return records.map((record, index) => { const evidenceId = toStringOrNull(record.evidence_id) ?? `ev-${fragmentId}-${index + 1}`; const claimRef = toStringOrNull(record.claim_ref) ?? (requirementIds[0] ? `requirement:${requirementIds[0]}` : `fragment:${fragmentId}`); const evidenceKind = inferEvidenceKind(record); const sourceType = normalizeEvidenceSourceType(record.source_type, record); const pointerResult = normalizeEvidencePointer(fragmentId, route, record, index); const mechanism = resolveMechanismNote(evidenceKind, record); const mechanismExpected = evidenceKind === "mechanism_link" || evidenceKind === "anomaly_signal" || evidenceKind === "aggregation"; const pointerWeak = pointerResult.fallback_source_namespace || pointerResult.fallback_source_entity || pointerResult.fallback_source_id; const limitation = resolveEvidenceLimitation({ record, sourceType, evidenceKind, mechanismReliable: mechanism.reliable, mechanismExpected, pointerWeak }); const confidence = resolveEvidenceConfidence({ explicitConfidence: parseEvidenceConfidence(record.confidence), sourceType, mechanismReliable: mechanism.reliable, mechanismExpected, limitation, pointerWeak }); return { evidence_id: evidenceId, claim_ref: claimRef, source_type: sourceType, source_ref: buildSourceRef(pointerResult.pointer), pointer: pointerResult.pointer, evidence_kind: evidenceKind, mechanism_note: mechanism.note, confidence, limitation, payload: record }; }); } export function normalizeRetrievalResult( fragmentId: string, requirementIds: string[], route: string, raw: unknown ): UnifiedRetrievalResult { const rawResult = normalizeRawRetrievalResult(raw); const items = normalizeObjectArray(rawResult.items); const summary = normalizeSummary(rawResult.summary); const evidence = normalizeEvidenceItems(fragmentId, requirementIds, route, rawResult.evidence); const baseResult: UnifiedRetrievalResult = { fragment_id: fragmentId, requirement_ids: requirementIds, route, status: normalizeStatus(rawResult.status), result_type: normalizeResultType(rawResult.result_type), items, summary, evidence, why_included: normalizeStringArray(rawResult.why_included), selection_reason: normalizeStringArray(rawResult.selection_reason), risk_factors: normalizeStringArray(rawResult.risk_factors), business_interpretation: normalizeStringArray(rawResult.business_interpretation), confidence: normalizeConfidence(rawResult.confidence), limitations: normalizeStringArray(rawResult.limitations), errors: normalizeErrors(rawResult.errors) }; if (!FEATURE_ASSISTANT_PROBLEM_UNITS_V1) { return baseResult; } const assembled = assembleProblemUnits({ route, result_type: baseResult.result_type, evidence, raw_entities: items, summary, risk_factors: baseResult.risk_factors, selection_reason: baseResult.selection_reason, business_interpretation: baseResult.business_interpretation }); const graphBuild = FEATURE_ASSISTANT_GRAPH_RUNTIME_V1 ? buildAccountingGraph({ route, candidateEvidence: assembled.candidate_evidence, problemUnits: assembled.problem_units }) : null; const graphBindingByUnitId = new Map(graphBuild?.unit_bindings.map((item) => [item.problem_unit_id, item] as const) ?? []); const graphBoundProblemUnits = assembled.problem_units.map((unit) => { const binding = graphBindingByUnitId.get(unit.problem_unit_id); if (!binding) { return unit; } return { ...unit, graph_binding: binding }; }); const graphBoundSummary = graphBuild ? { ...assembled.problem_unit_summary, graph_summary: graphBuild.summary } : assembled.problem_unit_summary; let enrichedSummary = mergeSummaryWithProblemUnitMeta(summary, { candidateEvidenceCount: assembled.candidate_evidence.length, problemUnitsCount: graphBoundProblemUnits.length, unitTypes: graphBoundSummary.unit_types, duplicateCollapses: graphBoundSummary.duplicate_collapses, severityDistribution: graphBoundSummary.severity_distribution, confidenceDistribution: graphBoundSummary.confidence_distribution, lifecycleEnrichedUnits: graphBoundSummary.lifecycle_enriched_units ?? 0, lifecycleDomainDistribution: normalizeNumberRecord(graphBoundSummary.lifecycle_domain_distribution), lifecycleDefectDistribution: normalizeNumberRecord(graphBoundSummary.lifecycle_defect_distribution) }); if (graphBuild) { enrichedSummary = mergeSummaryWithGraphMeta(enrichedSummary, graphBuild.summary); } return { ...baseResult, summary: enrichedSummary, raw_entities: items, candidate_evidence: assembled.candidate_evidence, problem_units: graphBoundProblemUnits, problem_unit_summary: graphBoundSummary, ...(graphBuild ? { accounting_graph: graphBuild } : {}) }; }