core/coordinators/graph.coordinator.ts
core/coordinators/graph.coordinator.ts is a file in Bane's Lab Build Scripts. 166 lines of code and 31 definitions.
import {
GRAPH_FIELD_RULES,
GRAPH_VOCABULARY,
PRODUCER_SUFFIX,
RECORDS_POPULATION,
SITE_LAYER,
} from "#configuration/constants/graph.constants";
import type { Graph, GraphBuild, GraphContext } from "#types/graph.types";
import {
ROLLED_UP_RELATIONS,
TOOLTIP_RELATIONS,
chunkKeyOf,
isListingRef,
} from "@banes-lab/web/constants/graph.constants";
import { absolutePath, relativePath } from "@ssot/paths";
import { canonicalOf, sourceFiles, toolsOf } from "#core/converters/source.converter";
import { coverage, layerPopulation, presence, tooltipGaps, uncoveredSections } from "#core/analyzers/graph.analyzer";
import { danglingEdges, mergeGraphs } from "#core/converters/graph.converter";
import { discover, tabsFromDiscovery } from "#core/converters/site.converter";
import { graphLines, missingPopulation } from "#configuration/strings/graph.strings";
import { nodeOf, teachingNodes } from "@banes-lab/content/core/converters/section.converter.ts";
import { sectionNumbers, siteCodes } from "#core/converters/graph.code.converter";
import { writeGraphChunks, writeGraphReport } from "#core/persistence/graph.persistence";
import type { BuiltOntology } from "#types/ontology.types";
import type { Discovery } from "#types/site.types";
import type { ModuleImporter } from "#types/loader.types";
import type { RouteStop } from "#types/learning.types";
import { buildLearningMap } from "#core/coordinators/learning.coordinator";
import { buildTabs } from "#core/persistence/tab.persistence";
import { chunksOf } from "#core/converters/graph.segment.converter";
import { createLinker } from "#core/resolvers/link.resolver";
import { evidenceTargetOf } from "#core/resolvers/evidence.resolver";
import { importFolder } from "#core/loaders/folder.loader";
import { loadContentGraphs } from "@banes-lab/content/core/loaders/coverage.loader.ts";
import { loadFrom } from "#core/loaders/site.loader";
import { loadWebModules } from "#core/loaders/catalog.loader";
import { ontologyGraph } from "#core/converters/graph.ontology.converter";
import { recordIdentities } from "#core/converters/record.converter";
import { registeredProducers } from "#core/registries/producer.registry";
import { renderSearchAsset } from "#core/formatters/search.formatter";
import { routeEdges } from "#core/converters/graph.link.converter";
import { routeSections } from "#core/analyzers/learning.analyzer";
import { routeStops } from "#core/converters/learning.catalog.converter";
import { searchAssetOf } from "#core/loaders/search.loader";
import { sectionPlans } from "#core/converters/section.converter";
import { withModuleServer } from "#core/factories/server.factory";
import { writeCanonicalText } from "@govlab/canonical-write";
export const buildGraph = async function buildGraph(
runner: ModuleImporter,
discovery: Discovery,
stops: readonly RouteStop[],
built: BuiltOntology,
): Promise<GraphBuild> {
const web = await loadWebModules(runner);
const tools = toolsOf(web);
const { collections } = built;
const trees = web.anatomy.ANATOMY_TREES.map((tree) => ({ ...tree, label: web.source.treeLabelOf(tree.tab) }));
const plans = sectionPlans(discovery, () => null);
const records = recordIdentities(collections, web.ontology.anchorHrefOf);
const identities = [
...plans.map((plan) => plan.identity),
...records,
...sourceFiles(trees, tools).map((file) => file.identity),
];
const linker = createLinker(identities, discovery.site, canonicalOf(discovery.site, tools));
const vocabulary = GRAPH_VOCABULARY;
const ontology = ontologyGraph(collections, vocabulary, GRAPH_FIELD_RULES);
const route = routeEdges(stops);
const roots = trees.map((tree) => tree.snapshot.tree);
const context: GraphContext = {
built,
codes: siteCodes(discovery.routes, plans, stops),
discovery,
evidenceSubjects: [...plans.map((plan) => plan.identity.ref), ...records.map((record) => record.ref)],
evidenceTarget: evidenceTargetOf(roots, web.definition.definitionIndexOf(roots), web.source),
ids: {
...tools,
folderHref: web.source.folderHref,
folderId: web.source.folderId,
nodeHref: web.source.nodeHref,
},
numbers: sectionNumbers(plans, stops),
ontology,
route,
scope: { linker, plans, web },
trees,
vocabulary,
};
await importFolder("app.graphProducers", PRODUCER_SUFFIX);
const merged = mergeGraphs(registeredProducers().map((producer) => producer.produce(context)));
const graph: Graph = {
edges: merged.edges,
nodes: merged.nodes.map((node) =>
node.href === null ? { ...node, href: linker.byRef(node.ref)?.href ?? null } : node,
),
};
const rules = {
contains: vocabulary.contains,
keptRelations: TOOLTIP_RELATIONS,
keyOf: chunkKeyOf,
pairs: GRAPH_VOCABULARY.pairs,
rollsUp: ROLLED_UP_RELATIONS,
skipsSource: isListingRef,
};
const chunks = chunksOf(graph, rules);
const sections = graph.nodes.filter((node) => node.layer === SITE_LAYER);
const teaching = new Set(teachingNodes(await loadContentGraphs()));
return {
chunks,
report: {
ambiguous: ontology.ambiguous,
dangling: danglingEdges(graph),
duplicates: merged.duplicates.map(([kept, dropped]) => ({ dropped, kept })),
fields: ontology.fields,
graph,
populations: [
...ontology.populations,
layerPopulation(graph.nodes),
presence("site numbers", sections, (node) => node.number !== null),
presence("node links", graph.nodes, (node) => node.href !== null),
...coverage(graph),
],
tooltipGaps: tooltipGaps(graph, chunks, rules),
uncovered: uncoveredSections(plans, teaching, graph, nodeOf),
undeclared: ontology.undeclared,
unresolved: [...ontology.unresolved, ...route.unresolved],
},
};
};
export const deriveGraph = async function deriveGraph(root: string, ontology: BuiltOntology): Promise<string> {
return withModuleServer(root, async (server) => {
const loaded = await loadFrom(server);
const discovery = await discover(loaded);
const tabs = tabsFromDiscovery(discovery);
const blocks = await buildLearningMap(loaded.links, tabs);
const routes = await buildTabs(loaded.links, tabs);
const stops = routeStops(blocks);
const built = await buildGraph(loaded.runner, discovery, stops, ontology);
const { chunks } = built;
const report = {
...built.report,
populations: [...built.report.populations, routeSections(await loadContentGraphs(), tabs)],
};
const search = await searchAssetOf(
loaded.runner,
stops.map((stop) => stop.path),
);
await writeCanonicalText(absolutePath("app.search"), renderSearchAsset(search));
const records = report.populations.find((population) => population.name === RECORDS_POPULATION)?.whole;
if (records === undefined) {
throw new Error(missingPopulation(RECORDS_POPULATION));
}
writeGraphReport(report);
await writeGraphChunks(chunks);
return graphLines(
{ chunks: chunks.size, records, report, routes, search, stops: stops.length },
{
graph: relativePath("app.graph"),
learning: relativePath("app.learning"),
report: relativePath("govlabHost.reports.content.graph"),
search: relativePath("app.search"),
tabs: relativePath("app.tabs"),
},
);
});
};