core/aggregators/concern.aggregator.ts
core/aggregators/concern.aggregator.ts is a file in GovLab Quality. 132 lines of code and 31 definitions.
import type { CatalogRule, ConcernControl, RuleKnob } from "#types/catalog.types";
import type { ConceptKnob, KnobConcept } from "#types/canon.types";
import { PRIMARY_KNOBS, SECONDARY_KNOBS, UNRANKED } from "#configuration/constants/knob.constants";
import { stringArrayField, stringField } from "#core/selectors/record.selector";
import { isDigit } from "@govlab/constants";
interface ConcernTally {
options: Set<string>;
rules: number;
thresholdDefaults: number[];
thresholdKnobByTool: Record<string, string>;
tools: Set<string>;
}
type KnobLookup = (tool: string, ruleId: string) => RuleKnob[];
const PRIMARY_RANK = new Map(PRIMARY_KNOBS.map((knob, rank) => [knob, rank]));
const numericDefault = function numericDefault(value: unknown): number | null {
if (typeof value === "number") {
return value;
}
const text = typeof value === "string" ? value : "";
let digits = "";
for (const char of text) {
if (!isDigit(char) && digits !== "") {
break;
}
digits += isDigit(char) ? char : "";
}
return digits === "" ? null : Number(digits);
};
const bridgedKnob = function bridgedKnob(knob: ConceptKnob | undefined): string | null {
if (knob === undefined || knob.fixed === true) {
return null;
}
return typeof knob.knob === "string" && knob.knob !== "" ? knob.knob : null;
};
const bridgeKnobs = function bridgeKnobs(
rules: readonly CatalogRule[],
knobConcepts: Readonly<Record<string, KnobConcept>>,
): Map<string, RuleKnob[]> {
const bridge = new Map<string, RuleKnob[]>();
for (const [concept, knobConcept] of Object.entries(knobConcepts)) {
for (const rule of rules) {
const tool = stringField(rule, "tool");
const toolKnob = knobConcept.tools[tool];
const knobName = bridgedKnob(toolKnob);
if (knobName !== null && stringArrayField(rule, "canonical").includes(concept)) {
const key = `${tool}:${rule.ruleId}`;
const knob: RuleKnob = { default: numericDefault(toolKnob?.default), knob: knobName, threshold: true };
bridge.set(key, [...(bridge.get(key) ?? []), knob]);
}
}
}
return bridge;
};
export const knobLookup = function knobLookup(
rules: readonly CatalogRule[],
ruleKnobs: Readonly<Record<string, RuleKnob[]>>,
knobConcepts: Readonly<Record<string, KnobConcept>>,
): KnobLookup {
const bridge = bridgeKnobs(rules, knobConcepts);
return (tool, ruleId) => {
const key = `${tool}:${ruleId}`;
const own = ruleKnobs[key] ?? [];
const seen = new Set(own.map((knob) => knob.knob));
return [...own, ...(bridge.get(key) ?? []).filter((knob) => !seen.has(knob.knob))];
};
};
const primaryThreshold = function primaryThreshold(knobs: readonly RuleKnob[]): { def: number; knob: string } | null {
const candidates = knobs
.filter((knob) => knob.threshold && knob.default !== null && knob.default !== undefined)
.filter((knob) => !SECONDARY_KNOBS.has(knob.knob.toLowerCase()))
.toSorted(
(a, b) =>
(PRIMARY_RANK.get(a.knob.toLowerCase()) ?? UNRANKED) -
(PRIMARY_RANK.get(b.knob.toLowerCase()) ?? UNRANKED),
);
const [primary] = candidates;
const def = primary ? numericDefault(primary.default) : null;
return primary && def !== null ? { def, knob: primary.knob } : null;
};
const tallyConcerns = function tallyConcerns(
rules: readonly CatalogRule[],
knobsFor: KnobLookup,
): Map<string, ConcernTally> {
const byConcern = new Map<string, ConcernTally>();
for (const rule of rules) {
const concern = stringField(rule, "concern");
if (concern !== "") {
const tool = stringField(rule, "tool");
const tally = byConcern.get(concern) ?? {
options: new Set<string>(),
rules: 0,
thresholdDefaults: [],
thresholdKnobByTool: {},
tools: new Set<string>(),
};
const knobs = knobsFor(tool, rule.ruleId);
tally.tools.add(tool);
tally.rules += 1;
for (const knob of knobs) {
tally.options.add(knob.knob);
}
const threshold = primaryThreshold(knobs);
if (threshold !== null) {
tally.thresholdDefaults.push(threshold.def);
tally.thresholdKnobByTool[tool] ??= threshold.knob;
}
byConcern.set(concern, tally);
}
}
return byConcern;
};
const controlOf = function controlOf(tally: ConcernTally): ConcernControl {
const control: ConcernControl = { rules: tally.rules, severity: "error", tools: tally.tools.size, value: true };
if (tally.thresholdDefaults.length > 0) {
const sorted = tally.thresholdDefaults.toSorted((a, b) => a - b);
control.value = Math.min(...tally.thresholdDefaults);
control.valueDerivation = `highest-lowest (strictest all rules settle on) = min of defaults [${sorted.join(", ")}]`;
control.knobPerTool = tally.thresholdKnobByTool;
}
if (tally.options.size > 0) {
control.configOptions = [...tally.options].toSorted((a, b) => a.localeCompare(b));
}
return control;
};
export const concernControls = function concernControls(
rules: readonly CatalogRule[],
knobsFor: KnobLookup,
): [string, ConcernControl][] {
return [...tallyConcerns(rules, knobsFor)]
.map(([id, tally]): [string, ConcernControl] => [id, controlOf(tally)])
.toSorted((a, b) => b[1].tools - a[1].tools || b[1].rules - a[1].rules);
};