513 lines
18 KiB
JavaScript
513 lines
18 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.normalizeRetrievalResult = normalizeRetrievalResult;
|
|
const config_1 = require("../config");
|
|
const stage1Contracts_1 = require("../types/stage1Contracts");
|
|
const problemUnitAssembler_1 = require("./problemUnitAssembler");
|
|
const stage4GraphRuntime_1 = require("./stage4GraphRuntime");
|
|
function toObject(value) {
|
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
return null;
|
|
}
|
|
return value;
|
|
}
|
|
function normalizeRawRetrievalResult(raw) {
|
|
const source = toObject(raw);
|
|
if (!source) {
|
|
return {};
|
|
}
|
|
return {
|
|
status: source.status,
|
|
result_type: source.result_type,
|
|
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) {
|
|
if (typeof value !== "string")
|
|
return null;
|
|
const trimmed = value.trim();
|
|
return trimmed.length > 0 ? trimmed : null;
|
|
}
|
|
function toNumberOrNull(value) {
|
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
return value;
|
|
}
|
|
return null;
|
|
}
|
|
function normalizeStatus(value) {
|
|
if (value === "ok" || value === "empty" || value === "partial" || value === "error") {
|
|
return value;
|
|
}
|
|
return "error";
|
|
}
|
|
function normalizeResultType(value) {
|
|
if (value === "list" || value === "summary" || value === "object" || value === "chain" || value === "ranking") {
|
|
return value;
|
|
}
|
|
return "summary";
|
|
}
|
|
function normalizeObjectArray(value) {
|
|
if (!Array.isArray(value)) {
|
|
return [];
|
|
}
|
|
return value
|
|
.map((item) => (item && typeof item === "object" ? item : null))
|
|
.filter((item) => item !== null);
|
|
}
|
|
function normalizeSummary(value) {
|
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
return {};
|
|
}
|
|
return value;
|
|
}
|
|
function normalizeErrors(value) {
|
|
if (!Array.isArray(value)) {
|
|
return [];
|
|
}
|
|
return value.map((item) => String(item));
|
|
}
|
|
function normalizeStringArray(value) {
|
|
if (!Array.isArray(value)) {
|
|
return [];
|
|
}
|
|
return value.map((item) => String(item));
|
|
}
|
|
function normalizeNumberRecord(value) {
|
|
const source = toObject(value);
|
|
if (!source) {
|
|
return {};
|
|
}
|
|
const result = {};
|
|
for (const [key, entry] of Object.entries(source)) {
|
|
if (typeof entry === "number" && Number.isFinite(entry)) {
|
|
result[key] = entry;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
function mergeSummaryWithProblemUnitMeta(summary, input) {
|
|
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, graphSummary) {
|
|
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) {
|
|
if (value === "high" || value === "medium" || value === "low") {
|
|
return value;
|
|
}
|
|
return "medium";
|
|
}
|
|
function parseEvidenceConfidence(value) {
|
|
if (value === "high" || value === "medium" || value === "low") {
|
|
return value;
|
|
}
|
|
return null;
|
|
}
|
|
function normalizeEvidenceNamespace(value) {
|
|
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) {
|
|
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, item) {
|
|
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.";
|
|
}
|
|
function resolveMechanismNote(kind, item) {
|
|
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) => Boolean(part))
|
|
.join("; ");
|
|
if (composed) {
|
|
return {
|
|
note: composed,
|
|
reliable: true
|
|
};
|
|
}
|
|
}
|
|
if (!config_1.FEATURE_ASSISTANT_EVIDENCE_ENRICHMENT_V1) {
|
|
return {
|
|
note: inferMechanismNoteLegacy(kind, item),
|
|
reliable: false
|
|
};
|
|
}
|
|
return {
|
|
note: null,
|
|
reliable: false
|
|
};
|
|
}
|
|
function normalizeEvidenceSourceType(value, record) {
|
|
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) {
|
|
const pointer = toObject(record.pointer);
|
|
return pointer ?? {};
|
|
}
|
|
function normalizeEvidencePointer(fragmentId, route, record, index) {
|
|
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) {
|
|
return encodeURIComponent((value ?? "none").trim().toLowerCase());
|
|
}
|
|
function buildSourceRef(pointer) {
|
|
return {
|
|
schema_version: stage1Contracts_1.EVIDENCE_SOURCE_REF_SCHEMA_VERSION,
|
|
namespace: pointer.source.namespace,
|
|
entity: pointer.source.entity,
|
|
id: pointer.source.id,
|
|
period: pointer.source.period,
|
|
canonical_ref: [
|
|
stage1Contracts_1.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) {
|
|
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) {
|
|
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";
|
|
}
|
|
function resolveEvidenceLimitation(input) {
|
|
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 (!config_1.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) {
|
|
if (value === "high")
|
|
return "medium";
|
|
if (value === "medium")
|
|
return "low";
|
|
return "low";
|
|
}
|
|
function resolveEvidenceConfidence(input) {
|
|
if (!config_1.FEATURE_ASSISTANT_EVIDENCE_ENRICHMENT_V1) {
|
|
return input.explicitConfidence ?? "medium";
|
|
}
|
|
let confidence = 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, requirementIds, route, value) {
|
|
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
|
|
};
|
|
});
|
|
}
|
|
function normalizeRetrievalResult(fragmentId, requirementIds, route, raw) {
|
|
const rawResult = normalizeRawRetrievalResult(raw);
|
|
const items = normalizeObjectArray(rawResult.items);
|
|
const summary = normalizeSummary(rawResult.summary);
|
|
const evidence = normalizeEvidenceItems(fragmentId, requirementIds, route, rawResult.evidence);
|
|
const baseResult = {
|
|
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 (!config_1.FEATURE_ASSISTANT_PROBLEM_UNITS_V1) {
|
|
return baseResult;
|
|
}
|
|
const assembled = (0, problemUnitAssembler_1.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 = config_1.FEATURE_ASSISTANT_GRAPH_RUNTIME_V1
|
|
? (0, stage4GraphRuntime_1.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]) ?? []);
|
|
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
|
|
}
|
|
: {})
|
|
};
|
|
}
|