core/converters/layer.converter.ts

core/converters/layer.converter.ts is a file in Bane's Lab Build Scripts. 145 lines of code and 30 definitions.

import { EDGE_KIND_VOCABULARY, MECHANISM_VOCABULARY } from "#configuration/constants/ontology.constants";
import type {
    ForceView,
    KindView,
    LayerEdgeView,
    LayerNodeView,
    LayerView,
    MembershipView,
    RelationRangeView,
    TensionView,
} from "@banes-lab/web/types/ontology.types.js";
import { type GovlabContext, KIND_TAXONOMY, RELATION_RANGES, slugify } from "@govlab/context";
import { idOf, refOf } from "#core/resolvers/ontology.resolver";
import { ALGO_FACE } from "@govlab/constants";
import type { EdgeRef } from "@banes-lab/web/types/link.types.js";
import type { Resolver } from "#types/ontology.types";

const ANTI_FORCE_MARK = "(";
const PAIR_SEPARATOR = "|";

const groupBy = function groupBy<T>(items: readonly T[], keyOf: (item: T) => string): readonly [string, T[]][] {
    const groups = new Map<string, T[]>();
    for (const item of items) {
        const key = keyOf(item);
        groups.set(key, [...(groups.get(key) ?? []), item]);
    }
    return [...groups.entries()];
};

const membershipOf = function membershipOf(context: GovlabContext, resolve: Resolver): readonly MembershipView[] {
    const layerRef = function layerRef(id: string): EdgeRef | null {
        const layer = context.layerOf(id);
        return layer === null ? null : resolve.layer(layer);
    };
    const fromArch = groupBy(context.arch.all(), (principle) => principle.category).map(([category, principles]) => {
        const first = principles.at(0);
        return { category: resolve.archCategory(category), layer: first === undefined ? null : layerRef(first.id) };
    });
    const archCategories = new Set(fromArch.map((entry) => slugify(entry.category.label)));
    const fromLex = groupBy(context.lex.all(), (term) => term.category)
        .filter(([category]) => !archCategories.has(category))
        .map(([category, terms]) => {
            const first = terms.at(0);
            return { category: resolve.lexCategory(category), layer: first === undefined ? null : layerRef(first.id) };
        });
    return [...fromArch, ...fromLex];
};

const pairKey = function pairKey(a: string, b: string): string {
    return [slugify(a), slugify(b)].sort((left, right) => left.localeCompare(right)).join(PAIR_SEPARATOR);
};

const resolutionsOf = function resolutionsOf(context: GovlabContext, resolve: Resolver): readonly TensionView[] {
    const explicit = new Set(context.resolutions().map((resolution) => pairKey(resolution.a, resolution.b)));
    const seen = new Set<string>();
    const out: TensionView[] = [];
    for (const principle of context.arch.all()) {
        for (const target of principle.tensions_with) {
            const key = pairKey(principle.id, target);
            const resolution = context.resolveTension(principle.id, target);
            if (resolution !== null && !seen.has(key)) {
                seen.add(key);
                const a = resolve.arch(resolution.a);
                const b = resolve.arch(resolution.b);
                out.push({
                    a,
                    b,
                    explicit: explicit.has(key),
                    id: idOf(resolve.tension(a, b).ref ?? key),
                    mechanism: resolve.vocabulary(MECHANISM_VOCABULARY, resolution.mechanism),
                    rule: resolution.rule,
                    scopeA: resolve.layer(resolution.scopeA),
                    scopeB: resolve.layer(resolution.scopeB),
                });
            }
        }
    }
    return out;
};

const nodesOf = function nodesOf(
    context: GovlabContext,
    resolve: Resolver,
    membership: readonly MembershipView[],
    topology: readonly LayerEdgeView[],
): readonly LayerNodeView[] {
    return context.layers().map((layer) => {
        const { ref } = resolve.layer(layer.id);
        return {
            contract: { label: layer.label, ref: refOf(ALGO_FACE, layer.id) },
            id: layer.id,
            incoming: topology.filter((edge) => edge.to.ref === ref),
            label: layer.label,
            members: membership.filter((entry) => entry.layer?.ref === ref).map((entry) => entry.category),
            outgoing: topology.filter((edge) => edge.from.ref === ref),
        };
    });
};

export const layersOf = function layersOf(context: GovlabContext, resolve: Resolver): LayerView {
    const membership = membershipOf(context, resolve);
    const topology = context
        .topology()
        .map((edge) => ({
            from: resolve.layer(edge.from),
            kind: resolve.vocabulary(EDGE_KIND_VOCABULARY, edge.kind),
            to: resolve.layer(edge.to),
        }));
    return {
        membership,
        nodes: nodesOf(context, resolve, membership, topology),
        resolutions: resolutionsOf(context, resolve),
        topology,
    };
};

export const kindsOf = function kindsOf(context: GovlabContext): readonly KindView[] {
    return KIND_TAXONOMY.map((kind) => ({
        definitionSignatures: kind.definitionSignatures,
        discriminator: kind.discriminator,
        distinguishesFrom: kind.distinguishesFrom,
        kind: kind.kind,
        principles: context.arch.all().filter((principle) => principle.type === kind.kind).length,
        terms: context.lex.all().filter((term) => term.kind === kind.kind).length,
    }));
};

export const rangesOf = function rangesOf(resolve: Resolver): readonly RelationRangeView[] {
    return Object.entries(RELATION_RANGES).map(([relation, kinds]) => ({
        kinds: [...kinds].map((kind) => resolve.kind(kind)),
        relation,
    }));
};

export const forceNames = function forceNames(context: GovlabContext): ReadonlySet<string> {
    return new Set(
        context
            .joinConcerns()
            .map((row) => row.force)
            .filter((force) => !force.includes(ANTI_FORCE_MARK)),
    );
};

export const forcesOf = function forcesOf(context: GovlabContext, resolve: Resolver): readonly ForceView[] {
    const known = forceNames(context);
    return context
        .joinConcerns()
        .filter((row) => known.has(row.force))
        .map((row) => ({
            concerns: row.concerns,
            contracts: row.contracts.map((contract) => resolve.algo(contract)),
            force: row.force,
            id: slugify(row.force),
            principles: row.principles.map((principle) => resolve.arch(principle)),
        }));
};