domain/converters/loop.converter.ts

domain/converters/loop.converter.ts is a file in Bane's Lab Site. 174 lines of code and 20 definitions.

import {
    AXIS_LABEL,
    CONTRACTS_LABEL,
    FIELD_SEPARATOR,
    FLOW_ARROW,
    GROUNDS_LABEL,
    LAYER_LABEL,
    MATH_TYPE_LABEL,
} from "#configuration/strings/ontology.strings";
import { EDGE_DOTTED, EDGE_LABEL_OPEN, EDGE_SOLID } from "#configuration/constants/diagram.constants";
import { FACE_SEPARATOR, REASON_FACE } from "@govlab/constants";
import {
    GATE_LABEL,
    LOOP_CAPTION,
    LOOP_INTRO,
    LOOP_SECTION_TITLE,
    MODEL_INTRO,
    MODEL_TITLE,
    ON_FAIL_LABEL,
    ON_PASS_LABEL,
    QUESTION_LABEL,
    RECURSION_LABEL,
    SEQUENCE_TITLE,
    SUBSTRATE_CAPTION,
    SUBSTRATE_INTRO,
    SUBSTRATE_SECTION_TITLE,
    TRANSITIONS_TITLE,
    TRANSITION_LABEL,
} from "#configuration/strings/reason.strings";
import type { LoopStageView, LoopTransitionView, SubstrateNodeView } from "#types/loop.types";
import { MODEL_SECTION_ID, REASON_ANCHOR, STAGE_ANCHOR, SUBSTRATE_SECTION_ID } from "#core/ids/ontology.ids";
import type { ModelView, ReasonView } from "#types/reason.types";
import type { Section, Subsection } from "#types/document.types";
import { chips, fields, glossary, labeled, linkOf, linkRow, linked, row } from "#domain/converters/ontology.converter";
import { diagram, edgeLine, node } from "#domain/converters/ontology.graph.converter";
import { REASON_SECTION_ICON } from "#configuration/icons/ontology.icons";
import type { ResolutionView } from "#types/ontology.types";
import { checkBlocks } from "#domain/converters/check.converter";
import { reasonRecord } from "#domain/converters/reason.fragment.converter";

const HEADER = "flowchart TB";
const HEADER_WIDE = "flowchart LR";
const GATE_KIND = "gates";
const REFUTE_KIND = "refutes-back";

const transitionLine = function transitionLine(transition: LoopTransitionView): string {
    if (transition.kind === REFUTE_KIND) {
        return edgeLine(transition.from.label, EDGE_DOTTED, transition.to.label);
    }
    if (transition.kind === GATE_KIND && transition.gate !== null) {
        return edgeLine(transition.from.label, EDGE_LABEL_OPEN + transition.gate + EDGE_SOLID, transition.to.label);
    }
    return edgeLine(transition.from.label, EDGE_SOLID, transition.to.label);
};

const loopDiagram = function loopDiagram(reason: ReasonView): string {
    const { derivationLoop } = reason;
    const nodes = derivationLoop.stages.map((stage) => node(stage.id, stage.id + FIELD_SEPARATOR + stage.axis.label));
    return diagram(HEADER, [...nodes, ...derivationLoop.transitions.map(transitionLine)]);
};

const stageSubsection = function stageSubsection(stage: LoopStageView, resolution: ResolutionView): Subsection {
    return {
        blocks: [
            chips([linked(AXIS_LABEL, stage.axis)]),
            glossary([...linkRow(CONTRACTS_LABEL, stage.contracts), ...linkRow(GROUNDS_LABEL, stage.edges)]),
            ...checkBlocks(resolution, REASON_FACE + FACE_SEPARATOR + stage.anchor),
        ],
        id: STAGE_ANCHOR + stage.id,
        title: stage.id,
    };
};

const transitionRow = function transitionRow(transition: LoopTransitionView): {
    readonly description: string;
    readonly term: string;
} {
    return row(
        transition.from.label + FLOW_ARROW + transition.to.label,
        fields([
            linkOf(transition.from) + FLOW_ARROW + linkOf(transition.to),
            labeled(TRANSITION_LABEL, transition.kind),
            labeled(GATE_LABEL, transition.gate),
            labeled(ON_PASS_LABEL, transition.onPass),
            labeled(ON_FAIL_LABEL, transition.onFail),
        ]),
    );
};

export const loopSection = function loopSection(reason: ReasonView, resolution: ResolutionView): Section {
    const { derivationLoop } = reason;
    return {
        blocks: [
            { caption: LOOP_CAPTION, kind: "mermaid", text: loopDiagram(reason) },
            ...checkBlocks(resolution, REASON_FACE + FACE_SEPARATOR + derivationLoop.anchor),
        ],
        icon: REASON_SECTION_ICON,
        id: REASON_ANCHOR + derivationLoop.anchor,
        intro: LOOP_INTRO,
        subsections: [
            ...derivationLoop.stages.map((stage) => stageSubsection(stage, resolution)),
            { blocks: [glossary(derivationLoop.transitions.map(transitionRow))], title: TRANSITIONS_TITLE },
        ],
        title: LOOP_SECTION_TITLE,
    };
};

const substrateDiagram = function substrateDiagram(reason: ReasonView): string {
    const { substrate } = reason;
    const nodes = substrate.cycle.map((stage) => node(stage.label, stage.label));
    const edges = substrate.cycle.flatMap((stage, index) => {
        const next = substrate.cycle[index + 1];
        return next === undefined ? [] : [edgeLine(stage.label, EDGE_SOLID, next.label)];
    });
    return diagram(HEADER_WIDE, [
        ...nodes,
        ...edges,
        edgeLine(substrate.recursionFrom.label, EDGE_DOTTED, substrate.recursionTo.label),
    ]);
};

const substrateSubsection = function substrateSubsection(
    entry: SubstrateNodeView,
    resolution: ResolutionView,
): Subsection {
    return {
        blocks: [
            chips([linked(LAYER_LABEL, entry.layer), linked(MATH_TYPE_LABEL, entry.mathType)]),
            ...checkBlocks(resolution, REASON_FACE + FACE_SEPARATOR + entry.anchor),
        ],
        id: REASON_ANCHOR + entry.anchor,
        title: entry.name,
    };
};

const modelSubsection = function modelSubsection(model: ModelView, resolution: ResolutionView): Subsection {
    const sequence = [...model.sequence.map(linkOf), ...model.steps].join(FLOW_ARROW);
    return reasonRecord(
        model.anchor,
        model.id,
        [
            chips([
                labeled(QUESTION_LABEL, model.question),
                model.recursionFrom === null || model.recursionTo === null
                    ? null
                    : labeled(RECURSION_LABEL, model.recursionFrom + FLOW_ARROW + model.recursionTo),
            ]),
            glossary([row(SEQUENCE_TITLE, sequence)]),
        ],
        resolution,
    );
};

export const modelSection = function modelSection(reason: ReasonView, resolution: ResolutionView): Section {
    return {
        icon: REASON_SECTION_ICON,
        id: MODEL_SECTION_ID,
        intro: MODEL_INTRO,
        subsections: reason.models.map((model) => modelSubsection(model, resolution)),
        title: MODEL_TITLE,
    };
};

export const substrateSection = function substrateSection(reason: ReasonView, resolution: ResolutionView): Section {
    const { substrate } = reason;
    return {
        blocks: [
            { caption: SUBSTRATE_CAPTION, kind: "mermaid", text: substrateDiagram(reason) },
            {
                kind: "text",
                text:
                    RECURSION_LABEL +
                    FIELD_SEPARATOR +
                    linkOf(substrate.recursionFrom) +
                    FLOW_ARROW +
                    linkOf(substrate.recursionTo),
            },
        ],
        icon: REASON_SECTION_ICON,
        id: SUBSTRATE_SECTION_ID,
        intro: SUBSTRATE_INTRO,
        subsections: substrate.nodes.map((entry) => substrateSubsection(entry, resolution)),
        title: SUBSTRATE_SECTION_TITLE,
    };
};