core/analyzers/check.analyzer.ts
core/analyzers/check.analyzer.ts is a file in GovLab Context. 127 lines of code and 15 definitions.
import {
BY_QUESTION,
CHECK_QUESTIONS,
DEPENDS_ON_QUESTION,
RECORD_KEY_SEPARATOR,
SECOND_CHECK_HOME,
SHAPE_QUESTION,
} from "#configuration/constants/check.constants";
import type { CheckGaps, CheckResolver, CheckedRecord, CollectionCoverage, DeclaredCheck } from "#types/check.types";
import { answered, isDeclaredAbsent } from "#core/predicates/check.predicate";
import { COLLECTIONS } from "#configuration/constants/ontology.constants";
import type { Faces } from "#types/context.types";
import { checkDeclarationDefectsOf } from "#core/validators/check.validator";
import { checkedRecordsOf } from "#core/selectors/check.selector";
import { slugify } from "#core/converters/identifier.converter";
const LEX_PREFIX = `${COLLECTIONS.lexicon}${RECORD_KEY_SEPARATOR}`;
const missingOf = function missingOf(record: CheckedRecord): string[] {
return [
...(record.by.length === 0 ? [BY_QUESTION] : []),
...CHECK_QUESTIONS.filter((question) => !answered(record.facet, question)),
];
};
const coverageOf = function coverageOf(collection: string, records: readonly CheckedRecord[]): CollectionCoverage {
const count = (predicate: (record: CheckedRecord) => boolean): number => records.filter(predicate).length;
return {
collection,
questions: [
{ answered: count((record) => record.by.length > 0), declaredAbsent: 0, question: BY_QUESTION },
...CHECK_QUESTIONS.map((question) => ({
answered: count((record) => answered(record.facet, question)),
declaredAbsent: count((record) => isDeclaredAbsent(record.facet, question)),
question,
})),
{ answered: count((record) => record.shape.length > 0), declaredAbsent: 0, question: SHAPE_QUESTION },
{
answered: count((record) => record.dependsOn.length > 0),
declaredAbsent: 0,
question: DEPENDS_ON_QUESTION,
},
],
records: records.length,
};
};
const isCollectionRef = function isCollectionRef(collections: ReadonlySet<string>, value: string): boolean {
const separator = value.indexOf(RECORD_KEY_SEPARATOR);
return separator > 0 && collections.has(value.slice(0, separator));
};
const refsOf = function refsOf(
record: CheckedRecord,
collections: ReadonlySet<string>,
): { field: string; ref: string }[] {
return [
...record.by.filter((ref) => isCollectionRef(collections, ref)).map((ref) => ({ field: BY_QUESTION, ref })),
...(record.collection === COLLECTIONS.lexicon
? record.dependsOn.map((ref) => ({
field: DEPENDS_ON_QUESTION,
ref: isCollectionRef(collections, ref) ? ref : `${LEX_PREFIX}${ref}`,
}))
: []),
];
};
const unreachedByPropagationOf = function unreachedByPropagationOf(faces: Faces): string[] {
const idByRef = new Map(
faces.arch
.all()
.flatMap((principle) =>
[principle.id, principle.name, ...(principle.aliases ?? [])].map((ref): [string, string] => [
slugify(ref),
principle.id,
]),
),
);
const targets = new Set(
faces.arch
.all()
.flatMap((principle) => [
...principle.requires,
...principle.reinforces,
...principle.enables,
...principle.conflicts_with,
...principle.tensions_with,
])
.map((target) => idByRef.get(slugify(target)) ?? target),
);
return faces.arch
.ids()
.filter((id) => !targets.has(id))
.toSorted((a, b) => a.localeCompare(b));
};
export const uncoveredCollectionsOf = function uncoveredCollectionsOf(
registered: readonly string[],
records: readonly CheckedRecord[],
): string[] {
const measured = new Set(records.map((record) => record.collection));
return registered.filter((name) => !measured.has(name));
};
export const checkGapsOf = function checkGapsOf(
faces: Faces,
resolver: CheckResolver,
registered: readonly string[],
checks: readonly DeclaredCheck[],
): CheckGaps {
const records = checkedRecordsOf(faces, checks);
const collections = [...new Set(records.map((record) => record.collection))];
return {
checkDeclarationDefects: checkDeclarationDefectsOf(faces, checks),
collections: collections.map((collection) =>
coverageOf(
collection,
records.filter((record) => record.collection === collection),
),
),
secondCheckHomes: records
.filter((record) => record.ownBy.length > 0)
.map((record) => ({ collection: record.collection, home: SECOND_CHECK_HOME, id: record.id })),
uncheckedRecords: records
.map((record) => ({ collection: record.collection, id: record.id, missing: missingOf(record) }))
.filter((entry) => entry.missing.length > 0),
uncoveredCollections: uncoveredCollectionsOf(registered, records),
unreachedByPropagation: unreachedByPropagationOf(faces),
unresolvedCheckRefs: records.flatMap((record) =>
refsOf(record, resolver.collections)
.filter(({ ref }) => !resolver.resolve(ref))
.map(({ field, ref }) => ({ collection: record.collection, field, id: record.id, ref })),
),
};
};