core/converters/finding.sequence.converter.ts

core/converters/finding.sequence.converter.ts is a file in GovLab Patterns. 81 lines of code and 10 definitions.

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

const transitionsFinding = function transitionsFinding(field: string, summary: SequenceSummary): Finding {
    const memory = summary.transitionSignificance;
    const verdict = memory.significant ? SEQUENCE_STRINGS.transitionsHasMemory : SEQUENCE_STRINGS.transitionsMemoryless;
    const observed = SEQUENCE_STRINGS.transitionsObserved(summary.changeRatio.toFixed(ROUNDING.standard));
    const explained = SEQUENCE_STRINGS.transitionsExplained(verdict, memory.pValue.toFixed(ROUNDING.fine));
    return makeFinding({
        coordinate: coordinate({
            analysis: "sequential",
            ontology: "change",
            reasoning: "explanation",
            representation: "symbolic",
        }),
        field,
        name: "transitions",
        narrative: reasonOf(observed, explained),
        observation: {
            changeRatio: fixedTo(summary.changeRatio, ROUNDING.fine),
            pValue: fixedTo(memory.pValue, ROUNDING.fine),
            significant: memory.significant,
            topTransitions: summary.topTransitions.map(([[from, to], count]) => ({ count, from, to })),
        },
        significance: {
            nullModel: NULL_MODEL_LABELS.transition,
            pValue: memory.pValue,
            significant: memory.significant,
            statistic: memory.statistic,
        },
    });
};

const runsFinding = function runsFinding(field: string, summary: SequenceSummary): Finding {
    return explanationFinding({
        analysis: "sequential",
        explained: SEQUENCE_STRINGS.runsExplained,
        field,
        name: "runs",
        observation: {
            longestRun: summary.longestRun,
            meanRun: fixedTo(summary.meanRun, ROUNDING.fine),
            runLengths: summary.runLengths.map(([length, count]) => ({ count, length })),
        },
        observed: SEQUENCE_STRINGS.runsObserved(summary.meanRun.toFixed(ROUNDING.standard), String(summary.longestRun)),
        ontology: "state",
        representation: "symbolic",
    });
};

const predictionFinding = function predictionFinding(field: string, summary: SequenceSummary): Finding | null {
    const [next] = summary.nextValue;
    if (!next || summary.lastValue === null) {
        return null;
    }
    const [value] = next;
    const observed = SEQUENCE_STRINGS.predictionObserved(summary.lastValue, value);
    const explained = SEQUENCE_STRINGS.predictionExplained(summary.markovAccuracy.toFixed(ROUNDING.standard));
    return makeFinding({
        coordinate: coordinate({
            analysis: "prediction",
            ontology: "change",
            reasoning: "prediction",
            representation: "dynamical-systems",
        }),
        field,
        name: "next-value-prediction",
        narrative: reasonOf(observed, explained),
        observation: { accuracy: summary.markovAccuracy, next: value },
        prediction: { accuracy: summary.markovAccuracy, method: PREDICTION_METHODS.markov },
    });
};

export const sequenceFindings = function sequenceFindings(field: string, summary: SequenceSummary): Finding[] {
    return withSupport(
        [transitionsFinding(field, summary), runsFinding(field, summary), predictionFinding(field, summary)],
        summary.count,
    );
};