# core/converters/vocabulary.converter.ts

> 186 lines of code and 37 definitions.

Tree: Bane's Lab Build Scripts
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/build-scripts#file-build-scripts-core-converters-vocabulary-converter-ts
Source text: https://banes-lab.com/source/build-scripts/core/converters/vocabulary.converter.ts.txt

Listed in [core/converters](https://banes-lab.com/api/source/build-scripts/core/converters.md), after [core/converters/view.converter.ts](https://banes-lab.com/source/build-scripts/core/converters/view.converter.ts.md) and before [core/converters/walk.converter.ts](https://banes-lab.com/source/build-scripts/core/converters/walk.converter.ts.md).

## Definitions

- `phrasesOf` (lexical_declaration, line 109)
- `pagRef` (lexical_declaration, line 138)
- `admit` (lexical_declaration, line 168, exported)
- `reasonEntry` (lexical_declaration, line 89)
- `layerOf` (lexical_declaration, line 174, exported)
- `pagPhrases` (lexical_declaration, line 142)
- `reasonEntries` (lexical_declaration, line 100)
- `reasonPhrases` (lexical_declaration, line 113)
- `isCode` (lexical_declaration, line 33)
- `principleEntries` (lexical_declaration, line 72)
- `termEntries` (lexical_declaration, line 83)
- `vocabularyOf` (lexical_declaration, line 165, exported)
- `aliasPhrasesOf` (lexical_declaration, line 155, exported)
- `splitName` (lexical_declaration, line 46)
- `PAREN_OPEN` (lexical_declaration, line 26)
- `PAREN_CLOSE` (lexical_declaration, line 27)
- `SPACE` (lexical_declaration, line 28)
- `CODE_LIMIT` (lexical_declaration, line 29)
- `UPPER` (lexical_declaration, line 30)
- `KEY_SAFE` (lexical_declaration, line 31)
- `capitals` (lexical_declaration, line 37)
- `open` (lexical_declaration, line 47)
- `close` (lexical_declaration, line 48)
- `inner` (lexical_declaration, line 52)
- `plain` (lexical_declaration, line 53)
- `keyOf` (lexical_declaration, line 57, exported)
- `layerLabels` (lexical_declaration, line 68)
- `ref` (lexical_declaration, line 77)
- `phrases` (lexical_declaration, line 79)
- `{ code, plain }` (lexical_declaration, line 84)
- `base` (lexical_declaration, line 85)
- `Aliased` (type_alias_declaration, line 107)
- `kinds` (lexical_declaration, line 114)
- `labels` (lexical_declaration, line 166, exported)
- `held` (lexical_declaration, line 167, exported)
- `key` (lexical_declaration, line 169, exported)
- `layerId` (lexical_declaration, line 175, exported)

## Contained in

- [core/converters](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-converters.md)

## Uses

- [core/resolvers/ontology.resolver.ts](https://banes-lab.com/source/build-scripts/core/resolvers/ontology.resolver.ts.md)

## Linked from

- [core/converters](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-converters.md)
- [core/resolvers](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-resolvers.md)

## Source

```typescript
import { ALGO_FACE, ARCH_FACE, LAYER_FACE, LEX_FACE, PAG_FACE, REASON_FACE } from "@govlab/constants";
import {
    AXIS_KIND,
    DIMENSION_KIND,
    FAILURE_SHAPE_KIND,
    INVARIANT_KIND,
    LENS_KIND,
    MATH_DOMAIN_KIND,
    MATH_TYPE_KIND,
    MODEL_KIND,
    MODE_KIND,
    NODE_KIND,
    PATTERN_TYPE_KIND,
    REASON_LAYER_KIND,
    REPRESENTATION_KIND,
    SUBSTRATE_NODE_KIND,
    TECHNIQUE_KIND,
    TEST_SURFACE_KIND,
    UNIVERSAL_AXIS_KIND,
} from "#configuration/constants/ontology.constants";
import { type GovlabContext, PAG_KINDS, type Principle, type Term, keywordIdOf } from "@govlab/context";
import { pagAnchor, reasonAnchor, refOf } from "#core/resolvers/ontology.resolver";
import type { Phrase } from "#types/search.types";
import type { VocabularyEntry } from "@banes-lab/web/types/vocabulary.types.js";

const PAREN_OPEN = "(";
const PAREN_CLOSE = ")";
const SPACE = " ";
const CODE_LIMIT = 6;
const UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const KEY_SAFE = "abcdefghijklmnopqrstuvwxyz0123456789";

const isCode = function isCode(candidate: string): boolean {
    if (candidate.length === 0 || candidate.length > CODE_LIMIT) {
        return false;
    }
    let capitals = 0;
    for (const char of candidate) {
        if (UPPER.includes(char)) {
            capitals += 1;
        }
    }
    return capitals >= 2;
};

const splitName = function splitName(name: string): { readonly code: string | null; readonly plain: string } {
    const open = name.indexOf(PAREN_OPEN);
    const close = name.indexOf(PAREN_CLOSE, open);
    if (open === -1 || close === -1) {
        return { code: null, plain: name.trim() };
    }
    const inner = name.slice(open + 1, close).trim();
    const plain = (name.slice(0, open) + name.slice(close + 1)).trim();
    return { code: isCode(inner) ? inner : null, plain };
};

export const keyOf = function keyOf(phrase: string): string {
    let key = "";
    for (const char of phrase.toLowerCase()) {
        key += KEY_SAFE.includes(char) ? char : SPACE;
    }
    return key
        .split(SPACE)
        .filter((word) => word.length > 0)
        .join(SPACE);
};

const layerLabels = function layerLabels(context: GovlabContext): ReadonlyMap<string, string> {
    return new Map(context.layers().map((layer) => [layer.id, layer.label]));
};

const principleEntries = function principleEntries(
    principle: Principle,
    layer: string | null,
): readonly VocabularyEntry[] {
    const { code, plain } = splitName(principle.name);
    const ref = refOf(ARCH_FACE, principle.id);
    const base = { code, kind: principle.type, layer, prose: true, ref };
    const phrases = [plain, ...(code === null ? [] : [code]), ...(principle.aliases ?? [])];
    return phrases.map((phrase) => ({ ...base, phrase }));
};

const termEntries = function termEntries(term: Term, layer: string | null): readonly VocabularyEntry[] {
    const { code, plain } = splitName(term.name);
    const base = { code, kind: term.kind, layer, prose: false, ref: refOf(LEX_FACE, term.id) };
    return [plain, ...term.aliases].map((phrase) => ({ ...base, phrase }));
};

const reasonEntry = function reasonEntry(kind: string, id: string, name: string): VocabularyEntry {
    return {
        code: null,
        kind,
        layer: null,
        phrase: name,
        prose: name.trim().includes(SPACE),
        ref: refOf(REASON_FACE, reasonAnchor(kind, id)),
    };
};

const reasonEntries = function reasonEntries(context: GovlabContext): readonly VocabularyEntry[] {
    return [
        ...context.reason.invariants().map((invariant) => reasonEntry(INVARIANT_KIND, invariant.id, invariant.name)),
        ...context.reason.nodes().map((node) => reasonEntry(NODE_KIND, node.id, node.name)),
    ];
};

type Aliased = readonly { readonly aliases?: string[]; readonly id: string }[];

const phrasesOf = function phrasesOf(ref: string, aliases: readonly string[] | undefined): readonly Phrase[] {
    return (aliases ?? []).map((phrase) => ({ phrase, ref }));
};

const reasonPhrases = function reasonPhrases({ reason }: GovlabContext): readonly Phrase[] {
    const kinds: readonly (readonly [string, Aliased])[] = [
        [NODE_KIND, reason.nodes()],
        [AXIS_KIND, reason.axes()],
        [REASON_LAYER_KIND, reason.layers()],
        [MATH_TYPE_KIND, reason.mathTypes()],
        [SUBSTRATE_NODE_KIND, reason.substrate().nodes],
        [DIMENSION_KIND, reason.dimensions()],
        [LENS_KIND, reason.lenses()],
        [MODE_KIND, reason.modes()],
        [REPRESENTATION_KIND, reason.representations()],
        [MATH_DOMAIN_KIND, reason.mathDomains()],
        [PATTERN_TYPE_KIND, reason.patternTypes()],
        [MODEL_KIND, reason.models()],
        [UNIVERSAL_AXIS_KIND, reason.universalAxes()],
        [TEST_SURFACE_KIND, reason.testSurfaces()],
        [TECHNIQUE_KIND, reason.techniques()],
        [INVARIANT_KIND, reason.invariants()],
        [FAILURE_SHAPE_KIND, reason.failureShapes()],
    ];
    return kinds.flatMap(([kind, records]) =>
        records.flatMap((record) => phrasesOf(refOf(REASON_FACE, reasonAnchor(kind, record.id)), record.aliases)),
    );
};

const pagRef = function pagRef(kind: string, key: string): string {
    return refOf(PAG_FACE, pagAnchor(kind, key));
};

const pagPhrases = function pagPhrases({ pag }: GovlabContext): readonly Phrase[] {
    return [
        ...pag
            .keywords()
            .flatMap((record) => phrasesOf(pagRef(PAG_KINDS.keyword, keywordIdOf(record)), record.aliases)),
        ...pag.productions().flatMap((record) => phrasesOf(pagRef(PAG_KINDS.production, record.lhs), record.aliases)),
        ...pag
            .documentTypes()
            .flatMap((record) => phrasesOf(pagRef(PAG_KINDS.documentType, record.type), record.aliases)),
        ...pag.templates().flatMap((record) => phrasesOf(pagRef(PAG_KINDS.template, record.type), record.aliases)),
    ];
};

export const aliasPhrasesOf = function aliasPhrasesOf(context: GovlabContext): readonly Phrase[] {
    return [
        ...context.arch.all().flatMap((record) => phrasesOf(refOf(ARCH_FACE, record.id), record.aliases)),
        ...context.lex.all().flatMap((record) => phrasesOf(refOf(LEX_FACE, record.id), record.aliases)),
        ...context.algo.all().flatMap((record) => phrasesOf(refOf(ALGO_FACE, record.id), record.aliases)),
        ...pagPhrases(context),
        ...reasonPhrases(context),
    ];
};

export const vocabularyOf = function vocabularyOf(context: GovlabContext): readonly VocabularyEntry[] {
    const labels = layerLabels(context);
    const held = new Map<string, VocabularyEntry>();
    const admit = function admit(entry: VocabularyEntry): void {
        const key = keyOf(entry.phrase);
        if (key.length > 0 && !held.has(key)) {
            held.set(key, entry);
        }
    };
    const layerOf = function layerOf(id: string): string | null {
        const layerId = context.layerOf(id);
        return layerId === null ? null : (labels.get(layerId) ?? layerId);
    };
    for (const principle of context.arch.all()) {
        for (const entry of principleEntries(principle, layerOf(principle.id))) {
            admit(entry);
        }
    }
    for (const layer of context.layers()) {
        admit({
            code: null,
            kind: LAYER_FACE,
            layer: layer.label,
            phrase: layer.label,
            prose: true,
            ref: refOf(LAYER_FACE, layer.id),
        });
    }
    for (const term of context.lex.all()) {
        for (const entry of termEntries(term, layerOf(term.id))) {
            admit(entry);
        }
    }
    for (const entry of reasonEntries(context)) {
        admit(entry);
    }
    return [...held.values()];
};
```
