core/converters/finding.graph.converter.ts

core/converters/finding.graph.converter.ts is a file in GovLab Patterns. 150 lines of code and 12 definitions.

import { GRAPH_STRINGS, NULL_MODEL_LABELS } from "#configuration/strings/representation.strings";
import { explanationFinding, makeFinding, reasonOf } from "#core/factories/finding.factory";
import type { Finding } from "#types/finding.types";
import type { GraphSummary } from "#types/representation.types";
import { ROUNDING } from "#configuration/constants/math.constants";
import { coordinate } from "#core/factories/axis.factory";
import { fixedTo } from "#core/normalizers/math.normalizer";
import { withSupport } from "#core/converters/finding.converter";

const LIFT_KEY = "lift";

const pairRows = function pairRows(
    rows: readonly [string[], number][],
    key: string,
): Readonly<Record<string, unknown>>[] {
    return rows.map(([pair, value]) => ({ [key]: key === LIFT_KEY ? fixedTo(value, ROUNDING.fine) : value, pair }));
};

const structureFindings = function structureFindings(field: string, summary: GraphSummary): Finding[] {
    return [
        explanationFinding({
            analysis: "relation",
            explained: GRAPH_STRINGS.cooccurrenceExplained,
            field,
            name: "cooccurrence",
            observation: {
                distinctTargets: summary.distinctTargets,
                edges: summary.edges,
                meanDegree: fixedTo(summary.meanDegree, ROUNDING.fine),
                topPairs: pairRows(summary.topPairs, "count"),
            },
            observed: GRAPH_STRINGS.cooccurrenceObserved(
                String(summary.distinctTargets),
                summary.meanDegree.toFixed(ROUNDING.standard),
            ),
            ontology: "relation",
            representation: "topology",
        }),
        explanationFinding({
            analysis: "relation",
            explained: GRAPH_STRINGS.liftExplained,
            field,
            name: LIFT_KEY,
            observation: { topLift: pairRows(summary.topLift, LIFT_KEY) },
            observed: GRAPH_STRINGS.liftObserved,
            ontology: "relation",
            representation: "probability",
        }),
        explanationFinding({
            analysis: "structure",
            explained: GRAPH_STRINGS.positionalExplained,
            field,
            name: "positional",
            observation: {
                positional: summary.positional.map(([index, value, count]) => ({ count, index, value })),
                slotPatterns: summary.slotPatterns.map(([index, from, to, count]) => ({ count, from, index, to })),
            },
            observed: GRAPH_STRINGS.positionalObserved,
            ontology: "structure",
            representation: "symbolic",
        }),
        explanationFinding({
            analysis: "structure",
            explained: GRAPH_STRINGS.combinatoricsExplained(summary.repeatRate.toFixed(ROUNDING.standard)),
            field,
            name: "combinatorics",
            observation: { distinctSets: summary.distinctSets, repeatRate: fixedTo(summary.repeatRate, ROUNDING.fine) },
            observed: GRAPH_STRINGS.combinatoricsObserved(String(summary.distinctSets)),
            ontology: "composition",
            representation: "computation",
        }),
    ];
};

const uniformityFinding = function uniformityFinding(field: string, summary: GraphSummary): Finding {
    const member = summary.memberUniformity;
    const verdict = member.uniform ? GRAPH_STRINGS.membersUniform : GRAPH_STRINGS.membersDepart;
    const observed = GRAPH_STRINGS.uniformityObserved(member.chiSquare.toFixed(ROUNDING.coarse), String(member.dof));
    return makeFinding({
        coordinate: coordinate({
            analysis: "statistical",
            ontology: "probability",
            reasoning: "explanation",
            representation: "probability",
        }),
        field,
        name: "member-uniformity",
        narrative: reasonOf(observed, GRAPH_STRINGS.uniformityExplained(verdict)),
        observation: {
            memberUniformity: member,
            topMembers: summary.topMembers.map(([value, count]) => ({ count, value })),
        },
        significance: {
            nullModel: NULL_MODEL_LABELS.memberUniformity,
            pValue: member.pValue,
            significant: !member.uniform,
            statistic: member.chiSquare,
        },
    });
};

const compositionFinding = function compositionFinding(field: string, summary: GraphSummary): Finding | null {
    const { composition } = summary;
    if (composition === null) {
        return null;
    }
    return explanationFinding({
        analysis: "structure",
        explained: GRAPH_STRINGS.compositionExplained(composition.highRatio.toFixed(ROUNDING.standard)),
        field,
        name: "composition",
        observation: {
            composition: {
                highRatio: fixedTo(composition.highRatio, ROUNDING.fine),
                oddRatio: fixedTo(composition.oddRatio, ROUNDING.fine),
            },
        },
        observed: GRAPH_STRINGS.compositionObserved(composition.oddRatio.toFixed(ROUNDING.standard)),
        ontology: "composition",
        representation: "number",
    });
};

const orderedFinding = function orderedFinding(field: string, summary: GraphSummary): Finding | null {
    const { ordered } = summary;
    if (ordered === null) {
        return null;
    }
    return explanationFinding({
        analysis: "space",
        explained: GRAPH_STRINGS.orderedExplained(ordered.symmetry.toFixed(ROUNDING.standard)),
        field,
        name: "ordered-domain",
        observation: {
            ordered: {
                adjacencyRate: fixedTo(ordered.adjacencyRate, ROUNDING.fine),
                bands: ordered.bands,
                symmetry: fixedTo(ordered.symmetry, ROUNDING.fine),
            },
        },
        observed: GRAPH_STRINGS.orderedObserved(ordered.adjacencyRate.toFixed(ROUNDING.standard)),
        ontology: "space",
        representation: "geometry",
    });
};

export const graphFindings = function graphFindings(field: string, summary: GraphSummary): Finding[] {
    return withSupport(
        [
            uniformityFinding(field, summary),
            ...structureFindings(field, summary),
            compositionFinding(field, summary),
            orderedFinding(field, summary),
        ],
        summary.records,
    );
};