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) },
            ];
        }),
    );
};