core/factories/quality.factory.ts
core/factories/quality.factory.ts is a file in GovLab Quality. 143 lines of code and 24 definitions.
import { EMPTY_CONCERN_ID, duplicateConcern, unknownResolvedConcern } from "#configuration/strings/quality.strings";
import type {
Indices,
Logger,
OntologyIssues,
QualityRelations,
QualityRelationsOptions,
ResolveResult,
RuleIndices,
} from "#types/quality.types";
import type { QualityConcept, QualityConcern, QualityData, QualityRule, QualityTool } from "#types/catalog.types";
import { loadQualityData } from "#core/loaders/quality.loader";
import { matchesFilter } from "#core/predicates/concern.predicate";
interface ConcernContribution {
concern: QualityConcern;
ruleGroup: QualityRule[];
knobPerTool?: Record<string, string> | undefined;
}
const indexById = function indexById<T extends { id: string }>(items: T[]): Map<string, T> {
return new Map(items.filter((item) => item.id).map((item): [string, T] => [item.id, item]));
};
const ensureIn = function ensureIn<T>(map: Map<string, T>, key: string, make: () => T): T {
const value = map.get(key) ?? make();
map.set(key, value);
return value;
};
const indexConcerns = function indexConcerns(concerns: QualityConcern[], logger?: Logger): Map<string, QualityConcern> {
const map = new Map<string, QualityConcern>();
for (const concern of concerns) {
if (!concern.id) {
logger?.warn(EMPTY_CONCERN_ID);
continue;
}
if (map.has(concern.id)) {
logger?.warn(duplicateConcern(concern.id));
}
map.set(concern.id, concern);
}
return map;
};
const indexRules = function indexRules(rules: QualityRule[]): RuleIndices {
const idx: RuleIndices = {
byRule: new Map(),
concernEcosystems: new Map(),
concernTools: new Map(),
rulesByConcern: new Map(),
};
for (const rule of rules) {
idx.byRule.set(rule.id, rule);
if (rule.concern) {
ensureIn(idx.rulesByConcern, rule.concern, () => []).push(rule);
ensureIn(idx.concernTools, rule.concern, () => new Set<string>()).add(rule.tool);
ensureIn(idx.concernEcosystems, rule.concern, () => new Set<string>()).add(rule.ecosystem);
}
}
return idx;
};
const buildIndices = function buildIndices(data: QualityData, logger?: Logger): Indices {
const concerns = data.concerns.map((record): QualityConcern => ({ ...record, id: record.name }));
const rules = data.rules.map((record): QualityRule => ({ ...record, id: record.name }));
const tools = data.tools.map((record): QualityTool => ({ ...record, id: record.name }));
const ruleIndices = indexRules(rules);
return {
...ruleIndices,
byConceptId: indexById(data.concepts),
byConcern: indexConcerns(concerns, logger),
byTool: indexById(tools),
concepts: data.concepts,
concerns,
rules,
tools,
};
};
const concernContribution = function concernContribution(
indices: Indices,
id: string,
logger?: Logger,
): ConcernContribution | null {
const concern = indices.byConcern.get(id);
if (!concern) {
logger?.warn(unknownResolvedConcern(id));
return null;
}
return { concern, knobPerTool: concern.knobPerTool, ruleGroup: [...(indices.rulesByConcern.get(id) ?? [])] };
};
const resolveImpl = function resolveImpl(indices: Indices, concernIds: string[], logger?: Logger): ResolveResult {
const contributions = concernIds
.map((id) => concernContribution(indices, id, logger))
.filter((contribution): contribution is ConcernContribution => contribution !== null);
const allRules = contributions.flatMap((contribution) => contribution.ruleGroup);
const touched = [...new Set(allRules.map((rule) => rule.tool))].sort((a, b) => a.localeCompare(b));
return {
concerns: contributions.map((contribution) => contribution.concern),
knobPerTool: Object.fromEntries(
contributions.flatMap((contribution) =>
contribution.knobPerTool ? [[contribution.concern.id, contribution.knobPerTool]] : [],
),
),
rules: allRules,
rulesByConcern: Object.fromEntries(
contributions.map((contribution) => [contribution.concern.id, contribution.ruleGroup]),
),
tools: touched.map((id) => indices.byTool.get(id)).filter((tool): tool is QualityTool => Boolean(tool)),
values: Object.fromEntries(
contributions.map((contribution) => [contribution.concern.id, contribution.concern.value]),
),
};
};
const validateImpl = function validateImpl(indices: Indices): OntologyIssues {
return {
concernsWithoutValue: [],
danglingRuleConcerns: indices.rules
.filter((rule) => rule.concern !== "" && !indices.byConcern.has(rule.concern))
.map((rule) => ({ concern: rule.concern, rule: rule.id })),
rulesWithoutConcern: indices.rules.filter((rule) => rule.concern === "").map((rule) => rule.id),
toolsWithoutEcosystem: indices.tools.filter((tool) => !tool.ecosystem).map((tool) => tool.id),
};
};
const ecosystemsOf = function ecosystemsOf(rules: QualityRule[]): string[] {
return [...new Set(rules.map((rule) => rule.ecosystem).filter(Boolean))].sort((a, b) => a.localeCompare(b));
};
export const createQualityRelations = function createQualityRelations(
options: QualityRelationsOptions = {},
): QualityRelations {
const { logger } = options;
const indices = buildIndices(options.data ?? loadQualityData(), logger);
return {
concept: (id): QualityConcept | null => indices.byConceptId.get(id) ?? null,
concepts: (): QualityConcept[] => [...indices.concepts],
concern: (id): QualityConcern | null => indices.byConcern.get(id) ?? null,
concerns: (): QualityConcern[] => [...indices.concerns],
ecosystems: (): string[] => ecosystemsOf(indices.rules),
query: (filter = {}): QualityConcern[] =>
indices.concerns.filter((concern) => matchesFilter(indices, concern, filter)),
resolve: (concernIds): ResolveResult => resolveImpl(indices, concernIds, logger),
rule: (id): QualityRule | null => indices.byRule.get(id) ?? null,
rules: (): QualityRule[] => [...indices.rules],
rulesOf: (concernId): QualityRule[] => [...(indices.rulesByConcern.get(concernId) ?? [])],
tool: (id): QualityTool | null => indices.byTool.get(id) ?? null,
tools: (): QualityTool[] => [...indices.tools],
validate: (): OntologyIssues => validateImpl(indices),
};
};