import { ANATOMY_FACE, CHAPTER_FACE } from "#configuration/constants/vocabulary.constants"; import { EVIDENCE_RELATION, GROUNDS_RELATION } from "#configuration/strings/folder.strings"; import type { Evidence, EvidenceNode, EvidenceSubject } from "#types/evidence.types"; import type { ReferenceContent, ReferenceRelation, ReferenceTarget } from "#types/reference.types"; import { chapterEdge, evidenceFor } from "#domain/converters/evidence.converter"; import { citedDefinition, resolveTarget } from "#domain/loaders/anatomy.loader"; import { fileId, folderHref, folderId, nodeHref } from "#domain/converters/source.converter"; import { resolveFolder, resolvePath } from "#core/loaders/definition.loader"; import { ANCHOR_PREFIX } from "#configuration/constants/document.constants"; import { EVIDENCE } from "#configuration/constants/evidence.source.constants"; import type { EdgeRef } from "#types/link.types"; import { FACE_SEPARATOR } from "@govlab/constants"; import type { NodeTarget } from "#types/folder.types"; import { PATH_SEPARATOR } from "#configuration/constants/anatomy.constants"; import { loadReferences } from "#assets/reference.generated"; import { targetOf } from "#core/converters/folder.converter"; interface Resolved { readonly edge: EdgeRef; readonly id: string; readonly line: number | null; } const treeRef = function treeRef(href: string): string { return CHAPTER_FACE + FACE_SEPARATOR + href; }; const resolveDefinition = function resolveDefinition(name: string, file: string | null): Resolved | null { const location = citedDefinition(name, file); return location === null ? null : { edge: { label: name, ref: treeRef(nodeHref(location.file, location.line)) }, id: fileId(location.file), line: location.line, }; }; const resolveFile = function resolveFile(name: string): Resolved | null { const path = resolvePath(name); return path === null ? null : { edge: { label: name, ref: treeRef(nodeHref(path, null)) }, id: fileId(path), line: null }; }; const resolveWords = function resolveWords(words: readonly string[]): Resolved | null { const path = resolveFolder(words.join(PATH_SEPARATOR)); return path === null ? null : { edge: { label: path, ref: treeRef(folderHref(path)) }, id: folderId(path), line: null }; }; export const resolveNode = function resolveNode(node: EvidenceNode): Resolved | null { if (node.kind === "definition") { return resolveDefinition(node.name, node.file); } return node.kind === "file" ? resolveFile(node.name) : resolveWords(node.words); }; const relation = function relation(name: string, edges: readonly EdgeRef[]): ReferenceRelation[] { return edges.length === 0 ? [] : [{ edges, relation: name }]; }; export const evidenceRelations = function evidenceRelations( target: ReferenceTarget, registry: readonly Evidence[] = EVIDENCE, ): ReferenceRelation[] { const edges = evidenceFor(target, registry).flatMap((entry) => entry.nodes.flatMap((node) => { const resolved = resolveNode(node); return resolved === null ? [] : [resolved.edge]; }), ); return relation(EVIDENCE_RELATION, edges); }; const holds = function holds(node: EvidenceNode, target: NodeTarget): boolean { const resolved = resolveNode(node); return resolved !== null && resolved.id === target.id && (resolved.line === null || resolved.line === target.line); }; const subjectEdge = async function subjectEdge(subject: EvidenceSubject): Promise { if (subject.kind === "chapter") { return chapterEdge(subject); } const face = subject.ref.slice(0, subject.ref.indexOf(FACE_SEPARATOR)); const record = (await loadReferences(face))?.[subject.ref]; return record === undefined ? null : { label: record.name, ref: subject.ref }; }; export const groundingRelations = async function groundingRelations( target: ReferenceTarget, registry: readonly Evidence[] = EVIDENCE, ): Promise { const requested = targetOf( ANCHOR_PREFIX + target.ref.slice(target.ref.indexOf(FACE_SEPARATOR) + FACE_SEPARATOR.length), ); if (requested === null) { return []; } const node = resolveTarget(requested); const subjects = registry .filter((entry) => entry.nodes.some((candidate) => holds(candidate, node))) .map((entry) => entry.subject); const edges = await Promise.all(subjects.map(subjectEdge)); return relation( GROUNDS_RELATION, edges.filter((edge): edge is EdgeRef => edge !== null), ); }; export const withEvidence = async function withEvidence( target: ReferenceTarget, content: ReferenceContent, ): Promise { if (content.kind === "chapter") { return { chapter: { ...content.chapter, relations: evidenceRelations(target) }, kind: "chapter" }; } const added = target.face === ANATOMY_FACE ? await groundingRelations(target) : evidenceRelations(target); return { kind: "record", record: { ...content.record, relations: [...content.record.relations, ...added] } }; };