/** * Linguistic Competence Metrics * * Privacy-preserving analysis of AAC *spoken output* (phrase history), based on: * - Niemeijer, Sheldon & Hillary Zisk (2025), "Measuring AAC user linguistic * competence: A novel approach", AssistiveWare (Communication Matters handout). * - Frisch, Wade et al. (2026), "It's Complicated: On the Design and Evaluation * of AI-Powered AAC Interfaces", arXiv:2606.24854. * * DESIGN (read me): * - **Source-agnostic.** The only input is `{ text, timestampMs }[]`. It does * not know or care whether the speech history came from Grid 3, Snap, * TouchChat, OBF/OBFL logs, or anything else. See `historyEntriesToCompetence* * Utterances` (in history.ts) to adapt any `HistoryEntry[]` source. * - **Language-agnostic core.** This module contains NO word lists. Language- * specific resources (a closed-class word set, an inflection classifier) are * INJECTED via `LanguageResources`. When a resource is missing for a * language, the affected measure is reported as `unavailable` with a reason * and a warning is raised — never silently wrong. * - **Pure / no I/O.** No filesystem, no platform APIs. Runs anywhere (browser * included) and emits only aggregate statistics (never the raw text). * * The four dimensions of linguistic competence (Light, 1989) and the measures we * use for each, following the AssistiveWare findings: * * Semantic -> MATTR-30 lexical diversity (always available) * Syntactic -> preposition/conjunction diversity (needs closedClassWords) * Morphological -> inflected-form diversity (needs classifyInflection) * Phonological -> proportion of unique words in a dictionary (needs a dictionary) * * All diversity measures use 30-word moving-average windows (Covington & McFall, * 2010), making them sample-length independent and usable for the tiny, highly * variable samples typical of AAC. MLU is intentionally NOT a headline (it * conflates linguistic/operational/strategic/social competence in AAC); it is * reported only as a distribution. */ import type { VocabularySummary } from './metrics/vocabularyDump'; /** A single spoken utterance with a production timestamp (epoch ms). */ export interface CompetenceUtterance { text: string; timestampMs: number; } /** A tokenised word stream produced in chronological order. */ export type WordStream = string[]; /** Coarse inflection category used by the (injected) morphology classifier. */ export type InflectionCategory = 'base' | 'plural' | 'possessive' | 'past' | 'progressive' | 'comparative' | 'superlative' | 'adverb'; /** * Language-specific resources, injected by the caller. Providing none leaves the * language-specific measures unavailable (with explicit warnings) — the semantic * measure still works for any language. */ export interface LanguageResources { /** Closed-class words (prepositions, conjunctions, ...) for syntactic diversity. */ closedClassWords?: Set; /** Maps a lowercased word to an inflection category for morphological diversity. */ classifyInflection?: (word: string) => InflectionCategory; } export interface DiversityOptions { /** Moving-average window size in words. The papers use 30. */ windowSize?: number; /** Closed-class word set (for the syntactic measure). */ closedClassWords?: Set; /** Inflection classifier (for the morphological measure). */ classifyInflection?: (word: string) => InflectionCategory; } export interface DiversityResult { /** Median of the per-window values (the headline figure, per the paper). */ median: number | null; mean: number | null; /** Number of windows that contributed a value (after any skipping). */ nWindows: number; /** The window size used. */ windowSize: number; /** Present when the measure could not be computed (e.g. missing language data). */ unavailable?: string; } /** * Tokenise raw text into a lowercased word stream. * Keeps intra-word apostrophes (don't, children's) but drops leading/trailing * punctuation and pure whitespace. Accented characters are preserved (\p{L}). */ export declare function tokenize(text: string): WordStream; /** * Moving-Average Type-Token Ratio (Covington & McFall, 2010). * * Slides a fixed-size window across the word stream and computes the TTR for * each window. The median across windows is sample-length independent, which is * exactly why it is preferred over plain TTR for highly variable AAC samples. */ export declare function movingAverageTTR(words: WordStream, windowSize?: number): DiversityResult; /** Convenience: MATTR-30 lexical diversity (the semantic headline). */ export declare function lexicalDiversity(words: WordStream, windowSize?: number): DiversityResult; /** * Closed-class diversity (generalises AssistiveWare's MA-UPC-TWR-30). * * For each window, compute the type-token ratio restricted to the supplied * closed-class words (prepositions + conjunctions in the original paper). Windows * containing none are skipped. The caller supplies the set via * `closedClassWords`, so this works for any language without hardcoding here. */ export declare function syntacticDiversity(words: WordStream, options?: DiversityOptions): DiversityResult; /** * Morphological diversity (proxy for MA-UMORPH-TLWR-30). * * Within each window, words the supplied classifier marks as inflected (any * category other than "base") contribute their surface forms to a type-token * ratio. Windows with no inflected words are skipped. The classifier is injected * (`classifyInflection`) so the heuristic lives with the caller, per language. * * Caveat (per AssistiveWare): for symbol-supported AAC, pre-stored morphology * buttons ("finished", "is", ...) heavily affect this measure — interpret trends * rather than absolutes. */ export declare function morphologicalDiversity(words: WordStream, options?: DiversityOptions): DiversityResult; /** * Proportion of unique alphabetic words present in the supplied dictionary. * Unique words only, so it is not skewed by repetition or repeated misspellings. * Returns null (unavailable) if no dictionary is provided. */ export declare function spellingValidity(words: WordStream, dictionary?: Set): number | null; export interface LexicalRichness { /** Brunet's index W = N · V^(-0.165). Range ~10–30; LOWER = richer. */ brunetsW: number | null; /** Honoré's statistic R = 100·ln N / (1 − V1/V). HIGHER = richer. */ honoresR: number | null; /** Hapax legomena (words used exactly once) — count only. */ hapax: number; /** Number of distinct words (V). */ types: number; /** Number of tokens (N). */ tokens: number; } export declare function lexicalRichness(words: WordStream): LexicalRichness; export interface DistributionStats { median: number | null; mean: number | null; p25: number | null; p75: number | null; n: number; } export interface ActivityStats { utterances: number; words: number; /** Distinct tokens — vocabulary breadth (count only, never the words). */ uniqueWords: number; activeDays: number; wordsPerUtterance: DistributionStats; } /** Compute engagement/activity stats for a set of utterances. */ export declare function summarizeActivity(utterances: CompetenceUtterance[]): ActivityStats; export interface MonthBin { /** Calendar month in local time, "YYYY-MM". */ month: string; utterances: number; words: number; uniqueWords: number; activeDays: number; wordsPerUtterance: DistributionStats; /** Semantic — the headline measure. */ lexicalDiversity: DiversityResult; /** Syntactic. null/unavailable when no closed-class data for the language. */ syntacticDiversity: DiversityResult; /** Morphological (proxy). null/unavailable when no classifier for the language. */ morphologicalDiversity: DiversityResult; /** Phonological — only when a dictionary is supplied. */ spellingValidity: number | null; /** Lexical richness indices (Brunet's W, Honoré's R) — always available. */ lexicalRichness: LexicalRichness; /** True when the month has too little data to trust the diversity figures. */ suppressed: boolean; suppressReason: string | null; } export interface TrendResult { metric: string; /** Slope of the weighted linear regression, in metric units per month. */ slopePerMonth: number | null; firstHalf: number | null; secondHalf: number | null; /** secondHalf - firstHalf. */ delta: number | null; direction: 'up' | 'down' | 'flat' | 'unknown'; } export interface DimensionSupport { available: boolean; reason?: string; } export interface PagesetSummary { label: string; gridsetIncluded: boolean; analysisVersion?: string; totalBoards: number; totalButtons: number; totalWords: number; grid: { rows: number; columns: number; }; effort: DistributionStats; hasDynamicPrediction: boolean; spellingEffort: { base: number | null; perLetter: number | null; }; /** * Counts-only vocabulary inventory (buttons, wordlists, prediction * dictionaries, smart-grammar word forms) — see dumpVocabulary(). * Null/undefined when the caller did not compute it. Contains counts only. */ vocabulary?: VocabularySummary | null; error?: string; } export interface UserSettingsSummary { startupGridSet: string | null; onlineAiToolsOptIn: boolean | null; accessMethods: string[]; personalisation: { pronunciations: number; capitalisations: number; abbreviationExpansions: number; smallWords: number; }; } export interface CompetenceReport { schema: string; generatedAt: string; privacy: { rawUtterancesIncluded: boolean; wordListsIncluded: boolean; fringeWordFrequencyIncluded: boolean; minAggregationWindowDays: number; notes: string[]; }; source: { platform: string; langCode?: string; userLabel?: string; dbPathIncluded: boolean; }; config: { months: number; windowSize: number; lang: string; minWordsPerMonth: number; dictionaryProvided: boolean; }; overall: { windowStart: string; windowEnd: string; totalUtterances: number; totalWords: number; monthsCovered: number; monthsSuppressed: number; }; timeline: MonthBin[]; trend: TrendResult; /** Per-dimension availability for the detected language (no silent degradation). */ support: { lang: string; semantic: DimensionSupport; syntactic: DimensionSupport; morphological: DimensionSupport; phonological: DimensionSupport; }; /** Human-readable notes about anything skipped or approximate. */ warnings: string[]; /** Structural metrics for the user's default gridset (null if unavailable). */ pageset?: PagesetSummary | null; /** System-configuration context (access method, AI opt-in, startup gridset). */ userSettings?: UserSettingsSummary | null; } export interface TimelineOptions { /** How many trailing months to analyse. Default 12. */ months?: number; /** Moving-average window. Default 30. */ windowSize?: number; /** Language code. Default "en". Used for reporting only. */ lang?: string; /** Months with fewer than this many words are flagged suppressed. Default 150. */ minWordsPerMonth?: number; /** Optional dictionary Set for the spelling measure. */ dictionary?: Set; /** Language-specific resources (closed-class words, inflection classifier). */ resources?: LanguageResources; /** Epoch ms for "now". Defaults to Date.now(). Mainly for tests. */ now?: number; /** Platform label for the report. Default "Grid3". */ platform?: string; userLabel?: string; langCode?: string; dbPathIncluded?: boolean; } /** * Analyse a corpus of utterances as a longitudinal competence report. * * Utterances are filtered to the trailing `months` window, binned by calendar * month, and each bin is scored on the four competence dimensions plus activity. * Language-specific measures require matching `resources`; missing resources are * reported under `support` and `warnings` rather than silently dropped. * * No raw text, word list, or fringe-vocabulary frequency is included in the * returned report — only aggregate statistics. */ export declare function analyzeTimeline(utterances: CompetenceUtterance[], options?: TimelineOptions): CompetenceReport;