core/converters/finding.distribution.converter.ts

core/converters/finding.distribution.converter.ts is a file in GovLab Patterns. 139 lines of code and 14 definitions.

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

const frequencyFinding = function frequencyFinding(field: string, summary: DistributionSummary): Finding {
    const { count, distinct, top, witnesses } = summary;
    return explanationFinding({
        analysis: "frequency",
        explained: DISTRIBUTION_STRINGS.freqExplained(summary.entropyBits.toFixed(ROUNDING.standard)),
        field,
        name: "frequency",
        observation: { count, distinct, top, witnesses },
        observed: DISTRIBUTION_STRINGS.freqObserved(String(count), String(distinct)),
        ontology: "probability",
        representation: "symbolic",
    });
};

const uniformityFinding = function uniformityFinding(field: string, summary: DistributionSummary): Finding {
    const { chiSquare, dof, pValue, uniform } = summary.uniformity;
    const verdict = uniform ? DISTRIBUTION_STRINGS.uniformUniform : DISTRIBUTION_STRINGS.uniformDeparts;
    const observed = DISTRIBUTION_STRINGS.uniformObserved(chiSquare.toFixed(ROUNDING.coarse), String(dof));
    const explained = DISTRIBUTION_STRINGS.uniformExplained(pValue.toFixed(ROUNDING.fine), verdict);
    return makeFinding({
        coordinate: coordinate({
            analysis: "statistical",
            ontology: "probability",
            reasoning: "explanation",
            representation: "probability",
        }),
        field,
        name: "uniformity",
        narrative: reasonOf(observed, explained),
        observation: { chiSquare, dof, pValue, uniform },
        significance: { nullModel: NULL_MODEL_LABELS.uniformity, pValue, significant: !uniform, statistic: chiSquare },
    });
};

const plainFindings = function plainFindings(field: string, summary: DistributionSummary): Finding[] {
    return [
        explanationFinding({
            analysis: "time",
            explained: DISTRIBUTION_STRINGS.recencyExplained,
            field,
            name: "recency",
            observation: { overdue: summary.overdue },
            observed: DISTRIBUTION_STRINGS.recencyObserved,
            ontology: "time",
            representation: "symbolic",
        }),
        explanationFinding({
            analysis: "frequency",
            explained: DISTRIBUTION_STRINGS.temperatureExplained,
            field,
            name: "temperature",
            observation: { cold: summary.cold, hot: summary.hot },
            observed: DISTRIBUTION_STRINGS.temperatureObserved,
            ontology: "change",
            representation: "number",
        }),
        explanationFinding({
            analysis: "change",
            explained: DISTRIBUTION_STRINGS.driftExplained,
            field,
            name: "drift",
            observation: { drift: summary.drift },
            observed: DISTRIBUTION_STRINGS.driftObserved,
            ontology: "change",
            representation: "number",
        }),
        explanationFinding({
            analysis: "complexity",
            explained: DISTRIBUTION_STRINGS.complexityExplained,
            field,
            name: "complexity",
            observation: { complexity: summary.complexity },
            observed: DISTRIBUTION_STRINGS.complexityObserved,
            ontology: "novelty",
            representation: "information-theory",
        }),
    ];
};

const seasonalityFinding = function seasonalityFinding(field: string, summary: DistributionSummary): Finding | null {
    const { temporal } = summary;
    if (temporal === null) {
        return null;
    }
    return explanationFinding({
        analysis: "time",
        explained: DISTRIBUTION_STRINGS.seasonalityExplained,
        field,
        name: "seasonality",
        observation: { temporal },
        observed: DISTRIBUTION_STRINGS.seasonalityObserved(temporal.first, temporal.last),
        ontology: "time",
        representation: "number",
    });
};

const predictionFinding = function predictionFinding(field: string, summary: DistributionSummary): Finding | null {
    const [top] = summary.top;
    if (!top) {
        return null;
    }
    const [mode] = top;
    const explained = DISTRIBUTION_STRINGS.predictionExplained(summary.modeAccuracy.toFixed(ROUNDING.standard));
    return makeFinding({
        coordinate: coordinate({
            analysis: "prediction",
            ontology: "probability",
            reasoning: "prediction",
            representation: "probability",
        }),
        field,
        name: "mode-prediction",
        narrative: reasonOf(DISTRIBUTION_STRINGS.predictionObserved(mode), explained),
        observation: { accuracy: summary.modeAccuracy, mode },
        prediction: { accuracy: summary.modeAccuracy, method: PREDICTION_METHODS.mode },
    });
};

export const distributionFindings = function distributionFindings(
    field: string,
    summary: DistributionSummary,
): Finding[] {
    return withSupport(
        [
            frequencyFinding(field, summary),
            uniformityFinding(field, summary),
            ...plainFindings(field, summary),
            seasonalityFinding(field, summary),
            predictionFinding(field, summary),
        ],
        summary.count,
    );
};