// types.ts — Complete type declarations for Calliope_TS pipeline // Reflects McAleese’s class diagrams (Figure 14) and additional phonological / // metrical types required by stress, phonological hierarchy, scansion, and Scandroid modules. /** * Stress levels in McAleese’s relative system, in ascending order * `x < w < n < m < s`. `x` is the "Zero Provision" tier (Hayes; Lerdahl & * Jackendoff 1983): a level *weaker than a stressless overt syllable*, borne by * maximally-reduced clitics (the/a/of/and…) and unfilled positions. */ export type StressLevel = 'x' | 'w' | 'n' | 'm' | 's'; /** Syntax-to-prosody derivation used for Calliope's Focus Primacy ranking. */ export type PhraseStressMode = 'wagner' | 'spe'; /** Meter-selection strategy used after the English phonological analysis. */ export type MeterScoringMode = 'current' | 'mcaleese'; /** Optional late second-opinion layer; `off` is the exact-compatibility default. */ /** `rerank` is retained as a backwards-compatible alias for `adaptive`. */ export type ProsodicCrosscheckMode = 'off' | 'advisory' | 'adaptive' | 'rerank'; /** A single syllable within a word, with phonetic and stress information */ export interface Syllable { text: string; // the orthographic syllable (or entire word if unsplit) phones: string; // CMU phonetic transcription (per-syllable ARPAbet) weight?: 'H' | 'L'; // syllable weight (Heavy/Light) stress: number; // numeric stress from CMU; modified by compound + nuclear rules lexicalStress?: number; // stress before nuclear rule; used for relative mapping / meter detection relativeStress?: StressLevel; // assigned after phrase‑stress rules and relative adjustment extrametrical?: 'morphological' | 'light_noun' | 'derivational'; // extrametricality classification } /** Represents a word in the dependency‑parse graph */ export interface ClsWord { index: number; // 1‑based index in the sentence (matching Antelope’s XML) lexicalClass: string; // POS tag, e.g., 'VBD', 'NN', 'PRP' lexicalDetails: string; // additional morphological info (empty if none) lexicalPlural: boolean; // true if plural position: string; // textual position (not always used) word: string; // the surface form of the word (case-normalised for lookups: // a sentence-initial capital is lowered unless proper noun) displayWord?: string; // the ORIGINAL surface form when it differs from `word` // (set by the parser's de-capitalisation) — what reports // and the phonopoetics show to the reader absoluteIndex: number; // 0‑based index among all words in the text isContent: boolean; // extended properties syllables: Syllable[]; // array of syllables for the word morphSuffix?: string; // productive suffix split off for OOV stress (e.g. 'est'); guides display syllabification morphPrefix?: string; // productive prefix split off for OOV stress (e.g. 'dis'); guides display syllabification phraseStress: number; // numeric phrase‑level stress after Nuclear Stress Rule dependency?: ClsDependency; // back‑reference to dependency edge (if any) node?: ClsNode; // back‑reference to the constituent node (if any) // ─── Calliope engine substrate (additive; ignored by the legacy/Clio path) ─── canonicalRel?: string; // normalised Scenario relation (NOMD/AMOD/VPRT/DOBJ/IOBJ/OBL/…) isPersonName?: boolean; // token is in the `humannames` list (proper noun = person) isPlaceName?: boolean; // token is in the `cities-list` list (proper name = place) // ─── Wagner/Krifka substrate (additive; 2026-06-29) ─── featsMap?: Record; // UD morphological FEATS parsed from lexicalDetails // (VerbForm/Voice/PronType/Number/Definite/Degree/Tense/…) discourseGiven?: boolean; // a content word repeated from an earlier line of the same // stanza — set only by the optional stanza-givenness pass // (analyzeStanzas / analyzeReadingDocument), never single-line coordinateGiven?: boolean; // a content word whose lemma is repeated as the HEAD of a // coordinate structure within the same line ("young blood and // high blood" → the second "blood" is anaphorically given; // contrastive focus falls on the modifier "high"). Set by the // relativiser's coordinate-givenness pre-pass. rhymeFocal?: boolean; // a line-final FUNCTION word that carries a strong end-rhyme // with a stressed (content) line-final elsewhere in the stanza // ("…one of THREE" / "…stopp'st thou ME") — the rhyme template // transfers a beat. Set only by the stanza rhyme-fellow pass // (analyzeStanzas / analyzeReadingDocument), never single-line. acuteSyllable?: number; // syllable index carrying the poet's explicit ACUTE diacritic // ("Milésien") — an overriding stress cue: the lexical primary // moves here, and the surface grade never sinks below 'm'. // Set by assignLexicalStress; honoured at the very end of // applySurfacePostProcessing, after every demotion pass. } /** A typed dependency edge between two words (as in Antelope’s XML) */ export interface ClsDependency { index: number; // 1‑based dependency index governorIndex: number; // word index of the governor (head) dependentIndex: number; // word index of the dependent dependentType: string; // type label: 'aux','nsubj','dobj','prep','det','poss','possessive','pobj',… governorName: string; // surface form of the governor dependentName: string; // surface form of the dependent governor: ClsWord; // reference to the governor word object dependent: ClsWord; // reference to the dependent word object } /** A constituent node in the parse tree (mirrors Figure 14) */ export interface ClsNode { index: string; // node identifier, e.g., '1', '1.2' nodeName: string; // label (e.g., 'SQ', 'NP', 'VP', 'PP', or a word index) parent: ClsNode | null; // parent node, null for root contains: (ClsNode | ClsWord)[]; // children: either sub‑nodes or words } /** A single parsed sentence */ export interface ClsSentence { index: number; // sentence number (1‑based) nodes: ClsNode | null; // root node of the parse tree dependencies: ClsDependency[]; // all dependency edges in this sentence words: ClsWord[]; // word objects in order xml: string; // serialised XML representation (optional) focusPrimacyMode?: PhraseStressMode; // derivation used for phraseStress (Calliope only) } /** Top‑level document containing parsed sentences */ export interface ClsDocument { sentences: ClsSentence[]; // list of parsed sentences xml: string; // full XML document (optional) } // ─── Phonological Hierarchy (CP, PP, IU) ──────────────────────────── /** A clitic group: one content word plus its attached function words */ export interface CliticGroup { tokens: ClsWord[]; } /** A phonological phrase: one or more clitic groups related syntactically */ export interface PhonologicalPhrase { cliticGroups: CliticGroup[]; } /** An intonational unit: one or more phonological phrases bounded by punctuation/line‑end */ export interface IntonationalUnit { phonologicalPhrases: PhonologicalPhrase[]; } // ─── Metre‑ and scansion‑related types ───────────────────────────── /** Recognised metre names */ export type MetreName = | 'iambic' | 'trochaic' | 'spondaic' | 'pyrrhic' | 'anapestic' | 'dactylic' | 'amphibrachic' | 'bacchic'; /** Definition of a metre candidate: its foot shape and syllable count per foot */ export interface MetreCandidate { name: MetreName; foot: string; // e.g., 'ws', 'sw', 'wws' syllableCount: number; // number of syllables in one foot (2 or 3) } /** A key‑stress pattern extracted from one unit of the phonological hierarchy */ export interface KeyStress { unitType: 'PW' | 'CP' | 'PP' | 'IU'; // type of prosodic unit pattern: string; // stress pattern, e.g., 'ws', 'sw', 'wsw' weight: number; // importance weight (1–3) as per McAleese’s scoring positions: number[]; // global syllable indices involved in this key stress } /** Result of scansion for a single line */ export interface ScansionResult { meter: MetreName | 'free verse'; // identified metre, or free verse if below threshold scansion: string; // the foot‑delimited scansion string, e.g., "ws|ws|ws|ws|ws" certainty: number; // percentage of maximum possible weighted score weightScore: number; // actual accumulated weight maxPossibleWeight: number; // theoretical maximum weight for the line algorithm?: string; // optional, to distinguish Phonological vs Scandroid results } /** One candidate meter's overall fit score (internal composite, not a probability) */ export interface MeterScore { meter: MetreName | 'free verse'; score: number; // scoreMeters' finalScore — a relative fit score, higher = better } /** One phonological-unit fragment examined by an alternate meter scorer. */ export interface MeterEvidenceSample { id: string; unitType: 'PW' | 'CP' | 'PP' | 'IU'; pattern: string; positions: number[]; weight: number; anchorPosition: number | null; boundaryStrength: number; } /** Auditable feature decomposition for one candidate meter. */ export interface MeterScoreBreakdown { meter: MetreName; score: number; fit: number; clean: number; unitEvidence: number; onset: number; prior: number; /** Realized ictus positions, exposed so continuity can detect grid aliases. */ beats?: number[]; /** Counted feet in this candidate's own licensed realization. */ footCount?: number; /** * Optional stanza-licensed cadence realization. A terminal substituted * Strong-Strong unit may realize two unary falling feet when the stanza's * independently established line length requires both weak positions to be * unfilled (Hayes/MacEachern-style truncation). */ cadenceFootCount?: number; cadenceScansion?: string; } /** Optional method-specific evidence accompanying a meter verdict. */ export interface MeterScoringDiagnostics { mode: MeterScoringMode; method: string; samples?: MeterEvidenceSample[]; candidates?: MeterScoreBreakdown[]; /** One or two weak line-final syllables treated as edge variation first. */ edgeEnding?: 'none' | 'feminine' | 'double-feminine'; edgeTailPositions?: number[]; } /** * Orthogonal scored families in the Calliope-native OT/HG tableau. Richer * lexical/syntactic/phonological provenance is retained separately, but a cue * contributes only once to its owning family. */ export interface ProsodicCrosscheckViolations { weakProminence: number; strongSupport: number; resolutionLicense: number; prosodicCohesion: number; edgeLicensing: number; } /** One local, non-zero contribution retained with its upstream provenance. */ export interface ProsodicCrosscheckViolationCell { family: keyof ProsodicCrosscheckViolations; amount: number; syllableIndex: number; positionId: number; sources: string[]; } /** One independent alternating-position grid retained for public diagnostics. */ export interface ProsodicCrosscheckGrid { pattern: string; strongMask: string; beats: number[]; harmonyLoss: number; violations: ProsodicCrosscheckViolations; footedScansion?: string; meter?: MetreName; } /** How one of Calliope's named-meter fits relates to the independent frontier. */ export interface ProsodicCrosscheckSupport { meter: MetreName; baseScore: number | null; baseRatio: number | null; /** Beat heads in this meter's best independent, actually enumerated grid. */ beats: number[]; /** Ictuses in Calliope's best substitution-bearing realization. */ realizedBeats: number[]; cleanRatio: number; fitFraction: number; gridPattern: string | null; onFrontier: boolean; /** Normalized total Calliope-native HG + metrical-form loss; lower is better. */ supportLoss: number; /** Distance from the best independent named-meter support. */ supportDelta: number; footedScansion: string | null; formLoss: number | null; substitutions: number; edgeOperations: string[]; nearestFrontierPattern: string | null; harmonyLoss: number | null; violations: ProsodicCrosscheckViolations | null; /** Local contributions for this actual selected grid; no cues are hidden. */ cells: ProsodicCrosscheckViolationCell[]; rationale: string[]; } /** Auditable result of the optional Prosodic-inspired late cross-check. */ export interface ProsodicCrosscheckReport { mode: Exclude; status: 'evaluated' | 'skipped'; method: string; reason: string; applied: boolean; originalMeter: string; originalMeterName: MetreName | 'free verse'; /** Best independent line-local meter before any contextual activation gate. */ independentMeter?: MetreName; independentConfidence?: number; /** Aggregate second opinion over every supplied stanza/work line. */ documentMeter?: MetreName; documentConfidence?: number; documentVotes?: Partial>; /** Lines that supplied an independently materialized local vote. */ documentEvaluatedLines?: number; /** Top meter's share of the confidence-weighted document vote. */ documentVoteShare?: number; /** Top-minus-runner-up share of the total document vote. */ documentVoteMargin?: number; recommendedMeter?: MetreName; finalMeter: string; /** Completed Calliope x/w/n/m/s input; W/S elsewhere denotes meter slots only. */ stressSequence?: string; syllableCount: number; candidateCount: number; frontierSize: number; frontierTruncated?: boolean; frontier: ProsodicCrosscheckGrid[]; support: ProsodicCrosscheckSupport[]; } /** Detailed phonological scansion for a single line */ export interface PhonologicalScansionDetail { all: string; // hierarchical string, e.g. "<{[nm/ws\n]}mn/sw\]m]}>" keyStresses: string; // key‑stress string, e.g. "<{[xx/ws\n]}xx/sw\]m]}>" meter: string; // e.g. "iambic pentameter" meterName: MetreName | 'free verse'; // enum value for tests footCount: number; // e.g. 5 summary: string; // e.g. "IU=1 PP=1 PW=1" counts per metre direction scansion: string; // foot‑separated, e.g. "xx/ws|nx/xs/wm" certainty: number; // 0‑100 weightScore: number; maxPossibleWeight: number; scoringMode?: MeterScoringMode; // absent only on frozen/third-party scorers scoringDiagnostics?: MeterScoringDiagnostics; /** Present only when the optional late Prosodic-inspired layer is enabled. */ prosodicCrosscheck?: ProsodicCrosscheckReport; ranking?: MeterScore[]; // candidate meters ranked by fit score (best first); optional consensusMeter?: string; // set by applyStanzaConsensus when this line's standalone meter // diverges from, yet closely fits, the stanza's dominant meter. // The continuity-rename pass (index.ts) then normally CONVERTS // this annotation: the line adopts the dominant meter as its BASE // reading (meter/scansion/footCount/certainty re-fitted under it), // consensusMeter is cleared, and standaloneMeter records the // numerically-best standalone reading. consensusMeter survives // only when the forced re-fit fails. standaloneMeter?: string; // the line's numerically best standalone meter (e.g. "dactylic // tetrameter"), kept when stanza/poem continuity renamed the base // reading to the dominant meter. rhythmNote?: string; // non-classical rhythm classification (Russian-metrics taxonomy): // "4-ictus dolnik", "3-ictus taktovik", "4-beat accentual", // "alternating 4·3-ictus accentual" — set at stanza level when // beat counts are regular but syllable counts vary and no // classical meter dominates; or per-line to refine a free-verse // reading. NOT a form verdict: "ballad" etc. belong to the // rhyme-aware form layer (rhyme.ts). metricalityNote?: string; // advisory prose-likeness hedge (scansion.ts), set when // a long, non-committal, weak-fit line reads as plausible // prose: "No consistent metered rhythm(s) discerned. …". // Display-only — never alters meter/scansion/certainty. rhyme?: { // rhyme annotation (rhyme.ts), LYRICAL-compatible typology. // Letters are assigned POEM-WIDE (a rhyme sound keeps its // letter across stanza breaks), in reading order. endWord: string; letter: string; // END-rhyme scheme letter 'A'/'B'/…, or '·' when unrhymed type?: string; // perfect/rich/family/assonant/consonant/augmented/ // diminished/wrenched/eye/identical matchedLine?: number; // 0-based poem line index this end-rhyme first binds to internal?: { // pre-caesural INTERNAL rhymes on this line (strong-tier // only), in left-to-right order; share the poem-wide // letter space with the end rhymes. word: string; letter: string; type?: string; }[]; notation?: string; // assembled scheme cell: internal letters parenthesised // then the end letter — e.g. "(A)B", "(C)C", "A", "·". }; formNote?: string; // stanza/poem FORM verdict (rhyme-aware): "ballad stanza // (ABCB, 4·3)", "blank verse", "Shakespearean Sonnet", // "terza rima (ABA BCB CDC…)", "limerick"… } /** Complete per‑line result from the pipeline */ export interface LineResult { sentence: ClsSentence; // parsed sentence with words, dependencies, nodes phonologicalHierarchy: IntonationalUnit[]; // CP/PP/IU structure keyStresses: KeyStress[]; // extracted key patterns with weights phonologicalScansion: PhonologicalScansionDetail; // scansion via phonological scoring // Charles Hartman's Scandroid (2005) run as a genuinely INDEPENDENT second // opinion: its own dictionary, its own syllabifier, its own two iambic // algorithms and its own anapestic engine, working ONLY from the raw line // text (src/scandroidNative/). Never derived from anything above. This is // the Calliope (main) pipeline's field. scandroidNative?: import('./scandroidNative/engine.js').ScandroidNativeLineResult; // Clio (the frozen legacy pipeline, src/clio/) still derives its Scandroid // comparison from Calliope-style stress grades via its own untouched // src/clio/scandroid.ts — kept here only so Clio's frozen code continues to // compile; the main pipeline never sets these. scandroidCorral?: ScansionResult; scandroidMaximise?: ScansionResult; } // ─── Display formatting types ────────────────────────────────────── /** Per-syllable information for display rendering */ export interface SyllableDisplayEntry { wordText: string; sylText: string; // orthographic text of this syllable sylIndex: number; // 0‑based index within the word sylCount: number; // total syllables in the word relativeStress: StressLevel; globalIndex: number; wordIndex: number; // 0‑based index among non-punctuation words } /** A single foot in the display, mapping scansion pattern to its syllables */ export interface FootDisplayEntry { footIndex: number; footPattern: string; // raw foot pattern from scansion, e.g., 'ws', '-ms' syllables: SyllableDisplayEntry[]; } /** All formatted display representations for a single line */ export interface FormattedDisplay { originalText: string; diacriticText: string; uppercaseText: string; ansiText: string; sylColoredText: string; footAligned: string; syllableBreakdown: string; } /** Options controlling display formatting */ export interface DisplayOptions { ansi: boolean; diacritics: boolean; footAligned: boolean; verbose: boolean; phrasal: boolean; }