core/validators/evidence.validator.ts
core/validators/evidence.validator.ts is a file in Bane's Lab Content. 94 lines of code and 16 definitions.
import {
CITE_NODE,
CITE_TREE_ONLY,
CITE_UNQUALIFIED,
GROUNDING_CONTRADICTED,
GROUNDING_UNASSESSED,
GROUNDING_UNDECLARED,
citeUnresolved,
} from "#configuration/strings/coverage.strings";
import type { ContentGraph, DeclaredAbsence, Evidence, GraphFinding, GroundingReport } from "#types/coverage.types";
import { PATH_SEPARATOR, TREE_MARK } from "@banes-lab/web/configuration/constants/anatomy.constants.ts";
import { nodeOf, nodePartsOf, teachingNodes } from "#core/converters/section.converter";
import { ANATOMY_TREE_DECLARATIONS } from "@banes-lab/web/core/registries/anatomy.tree.registry.ts";
import type { EvidenceNode } from "@banes-lab/web/types/evidence.types.js";
import { TREE_TAB } from "@banes-lab/web/core/ids/anatomy.ids.ts";
import { treeLabelOf } from "@banes-lab/web/domain/converters/source.converter.ts";
import { treeOf } from "@banes-lab/web/core/converters/folder.converter.ts";
const finding = function finding(node: string, message: string): GraphFinding {
return { message, ...nodePartsOf(node) };
};
const chapterNodes = function chapterNodes(evidence: readonly Evidence[]): ReadonlySet<string> {
return new Set(
evidence.flatMap((entry) =>
entry.subject.kind === "chapter" ? [nodeOf(entry.subject.page, entry.subject.section)] : [],
),
);
};
export const groundingOf = function groundingOf(
graphs: readonly ContentGraph[],
evidence: readonly Evidence[],
absences: readonly DeclaredAbsence[],
): GroundingReport {
const teaching = teachingNodes(graphs);
const declared = new Set(
graphs.flatMap((graph) => Object.keys(graph.sections).map((key) => nodeOf(graph.page, key))),
);
const grounded = chapterNodes(evidence);
const absent = new Set(
absences.flatMap((entry) =>
entry.subject.kind === "chapter" ? [nodeOf(entry.subject.page, entry.subject.section)] : [],
),
);
const unassessed = teaching.filter((node) => !grounded.has(node) && !absent.has(node));
const findings = [
...[...grounded].filter((node) => absent.has(node)).map((node) => finding(node, GROUNDING_CONTRADICTED)),
...[...grounded, ...absent]
.filter((node) => !declared.has(node))
.map((node) => finding(node, GROUNDING_UNDECLARED)),
...unassessed.map((node) => finding(node, GROUNDING_UNASSESSED)),
];
return {
absent: teaching.filter((node) => absent.has(node)),
findings,
grounded: teaching.filter((node) => grounded.has(node)),
unassessed,
};
};
const writtenOf = function writtenOf(node: EvidenceNode): string {
if (node.kind === "folder") {
return node.words.join(PATH_SEPARATOR);
}
return node.kind === "definition" && node.file !== null ? node.file : node.name;
};
const labelOf = function labelOf(node: EvidenceNode): string {
return node.kind === "folder" ? writtenOf(node) : node.name;
};
const citeFinding = function citeFinding(node: EvidenceNode, path: string | null): string | null {
if (path === null) {
return CITE_NODE + labelOf(node) + citeUnresolved(ANATOMY_TREE_DECLARATIONS.map((tree) => tree.label));
}
const tree = treeOf(path);
return tree === TREE_TAB || writtenOf(node).includes(TREE_MARK)
? null
: `${CITE_NODE}${labelOf(node)}${CITE_TREE_ONLY}${treeLabelOf(tree)}${CITE_UNQUALIFIED}${path}.`;
};
export const citeFindings = function citeFindings(
evidence: readonly Evidence[],
locate: (node: EvidenceNode) => string | null,
): readonly GraphFinding[] {
return evidence.flatMap((entry) =>
entry.nodes.flatMap((node) => {
const message = citeFinding(node, locate(node));
if (message === null) {
return [];
}
const { subject } = entry;
return [
subject.kind === "chapter"
? { message, page: subject.page, section: subject.section }
: { message, page: subject.ref, section: labelOf(node) },
];
}),
);
};