# core/formatters/coverage.formatter.ts

> 50 lines of code and 13 definitions.

Tree: GovLab Docs
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/docs#file-docs-core-formatters-coverage-formatter-ts
Source text: https://banes-lab.com/source/docs/core/formatters/coverage.formatter.ts.txt

Listed in [core/formatters](https://banes-lab.com/api/source/docs/core/formatters.md), after [core/formatters/catalog.formatter.ts](https://banes-lab.com/source/docs/core/formatters/catalog.formatter.ts.md) and before [core/formatters/diagram.formatter.ts](https://banes-lab.com/source/docs/core/formatters/diagram.formatter.ts.md).

## Definitions

- `withGap` (lexical_declaration, line 34)
- `gapsOf` (lexical_declaration, line 18)
- `renderCoverage` (lexical_declaration, line 46, exported)
- `blockingGaps` (lexical_declaration, line 42, exported)
- `coverageRow` (lexical_declaration, line 29, exported)
- `GAP_JOIN` (lexical_declaration, line 16)
- `{ manifest }` (lexical_declaration, line 19)
- `relationships` (lexical_declaration, line 20)
- `hasAiContext` (lexical_declaration, line 21)
- `isPrivate` (lexical_declaration, line 30, exported)
- `byGapsThenSlug` (lexical_declaration, line 38)
- `blocking` (lexical_declaration, line 47, exported)
- `ranked` (lexical_declaration, line 48, exported)

## Contained in

- [core/formatters](https://banes-lab.com/anatomy/docs/folder-docs-core-formatters.md)

## Uses

- [configuration/strings/coverage.strings.ts](https://banes-lab.com/source/docs/configuration/strings/coverage.strings.ts.md)
- [core/selectors/record.selector.ts](https://banes-lab.com/source/docs/core/selectors/record.selector.ts.md)

## Linked from

- [configuration/strings](https://banes-lab.com/anatomy/docs/folder-docs-configuration-strings.md)
- [core/formatters](https://banes-lab.com/anatomy/docs/folder-docs-core-formatters.md)
- [core/selectors](https://banes-lab.com/anatomy/docs/folder-docs-core-selectors.md)

## Source

```typescript
import {
    COVERAGE_COMPLETE,
    COVERAGE_GAPS,
    coverageGaps,
    coverageHeading,
    missingAiContext,
    missingCapabilities,
    rankedRow,
    withoutRelationships,
} from "#configuration/strings/coverage.strings";
import { arrayField, recordField } from "#core/selectors/record.selector";
import type { CoverageRow } from "#types/index.types";
import type { ManifestModule } from "#types/manifest.types";
import { RELATIONSHIP_KEYS } from "#configuration/constants/manifest.constants";

const GAP_JOIN = ", ";

const gapsOf = function gapsOf(module: ManifestModule, isPrivate: boolean): string[] {
    const { manifest } = module;
    const relationships = RELATIONSHIP_KEYS.reduce((sum, key) => sum + arrayField(manifest, key).length, 0);
    const hasAiContext = Boolean(recordField(manifest, "docs")?.["aiContext"]);
    return [
        ...(arrayField(manifest, "capabilities").length === 0 ? [COVERAGE_GAPS.capabilities] : []),
        ...(!isPrivate && !hasAiContext ? [COVERAGE_GAPS.aiContext] : []),
        ...(relationships === 0 ? [COVERAGE_GAPS.relationships] : []),
    ];
};

export const coverageRow = function coverageRow(module: ManifestModule): CoverageRow {
    const isPrivate = recordField(module.manifest, "visibility")?.["private"] === true;
    return { gaps: gapsOf(module, isPrivate), isPrivate, slug: module.label };
};

const withGap = function withGap(rows: readonly CoverageRow[], gap: string): number {
    return rows.filter((row) => row.gaps.includes(gap)).length;
};

const byGapsThenSlug = function byGapsThenSlug(left: CoverageRow, right: CoverageRow): number {
    return right.gaps.length - left.gaps.length || left.slug.localeCompare(right.slug);
};

export const blockingGaps = function blockingGaps(rows: readonly CoverageRow[]): number {
    return withGap(rows, COVERAGE_GAPS.capabilities) + withGap(rows, COVERAGE_GAPS.aiContext);
};

export const renderCoverage = function renderCoverage(rows: readonly CoverageRow[]): string[] {
    const blocking = blockingGaps(rows);
    const ranked = rows.filter((row) => row.gaps.length > 0).toSorted(byGapsThenSlug);
    return [
        coverageHeading(rows.length),
        missingCapabilities(withGap(rows, COVERAGE_GAPS.capabilities)),
        missingAiContext(withGap(rows, COVERAGE_GAPS.aiContext)),
        withoutRelationships(withGap(rows, COVERAGE_GAPS.relationships)),
        ...ranked.map((row) => rankedRow(row.slug, row.isPrivate, row.gaps.join(GAP_JOIN))),
        blocking === 0 ? COVERAGE_COMPLETE : coverageGaps(blocking),
    ];
};
```
