domain/converters/principle.converter.ts

domain/converters/principle.converter.ts is a file in Bane's Lab Site. 152 lines of code and 14 definitions.

import {
    AFTER_TITLE,
    ALIASES_LABEL,
    BEFORE_TITLE,
    CANON_LABEL,
    CATEGORY_CAPTION,
    CATEGORY_INTRO,
    CATEGORY_LABEL,
    CONFLICTS_WITH_LABEL,
    CONTRACTS_LABEL,
    DETECTED_BY_LABEL,
    DISTINCT_FROM_LABEL,
    ENABLES_LABEL,
    ENFORCED_BY_LABEL,
    EXPRESSED_BY_LABEL,
    FORMED_BY_LABEL,
    KIND_LABEL,
    LABEL_SEPARATOR,
    LAYER_LABEL,
    MANDATORY_FOR_LABEL,
    MEASURED_BY_LABEL,
    REFACTORED_BY_LABEL,
    REFERENCED_BY_LABEL,
    REINFORCES_LABEL,
    REQUIRES_LABEL,
    SCOPE_LABEL,
    SEVERITY_LABEL,
    TENSIONS_LABEL,
    TENSIONS_WITH_LABEL,
    TERM_LABEL,
    VIOLATED_BY_LABEL,
} from "#configuration/strings/ontology.strings";
import { ARCH_ANCHOR, ARCH_CATEGORY_ANCHOR } from "#core/ids/ontology.ids";
import { ARCH_FACE, FACE_SEPARATOR } from "@govlab/constants";
import { EDGE_DOTTED, EDGE_SOLID } from "#configuration/constants/diagram.constants";
import type { PrincipleCategoryView, PrincipleView, ResolutionView } from "#types/ontology.types";
import type { Section, Subsection } from "#types/document.types";
import { canonOf, checkBlocks } from "#domain/converters/check.converter";
import {
    chips,
    exemplarBlocks,
    glossary,
    labeled,
    linkList,
    linkOf,
    linkRow,
    linked,
    plainList,
    row,
    wideRow,
} from "#domain/converters/ontology.converter";
import { diagram, edgeLine, localId, node } from "#domain/converters/ontology.graph.converter";
import type { Block } from "#types/block.types";
import { CATEGORY_SECTION_ICON } from "#configuration/icons/ontology.icons";
import { DEFINITION_LABEL } from "#configuration/strings/lexicon.strings";
import type { EdgeRef } from "#types/link.types";
import { groupTitleOf } from "#configuration/strings/catalog.strings";

const HEADER = "flowchart LR";
const NEGATIVE = " --x ";

const facts = function facts(principle: PrincipleView): Block {
    return chips([
        linked(KIND_LABEL, principle.kind),
        linked(CATEGORY_LABEL, principle.category),
        linked(SEVERITY_LABEL, principle.severity),
        labeled(MANDATORY_FOR_LABEL, principle.mandatoryFor),
        principle.scope.length === 0 ? null : labeled(SCOPE_LABEL, linkList(principle.scope)),
        principle.aliases.length === 0 ? null : labeled(ALIASES_LABEL, plainList(principle.aliases)),
        linked(LAYER_LABEL, principle.layer),
    ]);
};

const relations = function relations(principle: PrincipleView): Block {
    return glossary([
        row(REQUIRES_LABEL, linkList(principle.edges.requires)),
        row(REINFORCES_LABEL, linkList(principle.edges.reinforces)),
        row(ENABLES_LABEL, linkList(principle.edges.enables)),
        row(TENSIONS_WITH_LABEL, linkList(principle.edges.tensionsWith)),
        row(CONFLICTS_WITH_LABEL, linkList(principle.edges.conflictsWith)),
        ...linkRow(REFERENCED_BY_LABEL, principle.referencedBy),
        ...linkRow(TERM_LABEL, principle.term === null ? [] : [principle.term]),
        ...linkRow(CONTRACTS_LABEL, principle.contracts),
        ...linkRow(TENSIONS_LABEL, principle.tensions),
        ...linkRow(EXPRESSED_BY_LABEL, principle.expressedBy),
        ...principle.distinctFrom.map((entry) =>
            row(DISTINCT_FROM_LABEL, linkOf(entry.record) + LABEL_SEPARATOR + entry.reason),
        ),
    ]);
};

const descriptors = function descriptors(principle: PrincipleView): Block {
    return glossary([
        principle.formedBy === null
            ? row(VIOLATED_BY_LABEL, linkList(principle.violatedBy))
            : row(FORMED_BY_LABEL, principle.formedBy),
        row(DETECTED_BY_LABEL, linkList(principle.detectedBy)),
        row(MEASURED_BY_LABEL, linkList(principle.measuredBy)),
        row(REFACTORED_BY_LABEL, linkList(principle.refactoredBy)),
        row(ENFORCED_BY_LABEL, linkList(principle.enforcedBy)),
        ...(principle.canon.length === 0 ? [] : [row(CANON_LABEL, canonOf(principle.canon))]),
    ]);
};

const principleSubsection = function principleSubsection(
    principle: PrincipleView,
    resolution: ResolutionView,
): Subsection {
    return {
        blocks: [
            facts(principle),
            glossary([wideRow(DEFINITION_LABEL, principle.definition)]),
            relations(principle),
            descriptors(principle),
            ...exemplarBlocks(principle.exemplar, BEFORE_TITLE, AFTER_TITLE),
            ...checkBlocks(resolution, ARCH_FACE + FACE_SEPARATOR + principle.id),
        ],
        id: ARCH_ANCHOR + principle.id,
        title: principle.name,
    };
};

const categoryDiagram = function categoryDiagram(group: PrincipleCategoryView): string {
    const members = new Set(group.principles.map((principle) => principle.id));
    const nodes = group.principles.map((principle) => node(principle.id, principle.name));
    const edges = group.principles.flatMap((principle) => {
        const arrows: [readonly EdgeRef[], string][] = [
            [principle.edges.requires, EDGE_SOLID],
            [principle.edges.reinforces, EDGE_SOLID],
            [principle.edges.enables, EDGE_SOLID],
            [principle.edges.tensionsWith, EDGE_DOTTED],
            [principle.edges.conflictsWith, NEGATIVE],
        ];
        return arrows.flatMap(([targets, arrow]) =>
            targets.flatMap((target) => {
                const id = localId(target, ARCH_FACE, members);
                return id === null || id === principle.id ? [] : [edgeLine(principle.id, arrow, id)];
            }),
        );
    });
    return diagram(HEADER, [...nodes, ...edges]);
};

const categorySection = function categorySection(group: PrincipleCategoryView, resolution: ResolutionView): Section {
    return {
        blocks: [{ caption: CATEGORY_CAPTION, kind: "mermaid", text: categoryDiagram(group) }],
        icon: CATEGORY_SECTION_ICON,
        id: ARCH_CATEGORY_ANCHOR + group.id,
        intro: CATEGORY_INTRO,
        subsections: group.principles.map((principle) => principleSubsection(principle, resolution)),
        title: groupTitleOf(group.id, group.category),
    };
};

export const principleSections = function principleSections(
    groups: readonly PrincipleCategoryView[],
    resolution: ResolutionView,
): readonly Section[] {
    return groups.map((group) => categorySection(group, resolution));
};