@capsiynau/intelligence 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +65 -0
- package/README.md +35 -4
- package/package.json +33 -7
- package/src/corrections/index.js +11 -1
- package/src/corrections/pure.js +133 -0
- package/src/corrections/tracker.js +36 -116
- package/src/speakers/index.js +204 -0
- package/src/spellcheck/core.js +73 -6
- package/src/welsh/normaliser.js +11 -0
- package/types/corrections/index.d.ts +2 -0
- package/types/corrections/pure.d.ts +21 -0
- package/types/corrections/tracker.d.ts +36 -0
- package/types/glossary/import.d.ts +55 -0
- package/types/glossary/index.d.ts +6 -0
- package/types/glossary/parse.d.ts +63 -0
- package/types/glossary/persist.d.ts +66 -0
- package/types/glossary/prompts.d.ts +10 -0
- package/types/glossary/tools.d.ts +4 -0
- package/types/glossary/url.d.ts +9 -0
- package/types/index.d.ts +2 -0
- package/types/speakers/index.d.ts +85 -0
- package/types/spellcheck/core.d.ts +74 -0
- package/types/spellcheck/index.d.ts +1 -0
- package/types/welsh/digraphs.d.ts +102 -0
- package/types/welsh/index.d.ts +3 -0
- package/types/welsh/mutations.d.ts +76 -0
- package/types/welsh/normaliser.d.ts +13 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Total milliseconds the utterances cover WITHIN the transcript span
|
|
3
|
+
* `[spanStart, spanEnd]`, counted as a UNION so overlapping utterances don't
|
|
4
|
+
* double-count. Each utterance is first clipped to the span (speech entirely
|
|
5
|
+
* outside the transcript window contributes 0), then the clipped intervals are
|
|
6
|
+
* merged and their lengths summed. This is what makes coverage measure the
|
|
7
|
+
* fraction of the transcript actually overlapped by the speaker timeline,
|
|
8
|
+
* rather than total diarisation duration (which can sit outside the window
|
|
9
|
+
* when diarisation ran over the full audio but the transcript is a clip).
|
|
10
|
+
*
|
|
11
|
+
* @param {Array<{start?:number,end?:number}>} utts
|
|
12
|
+
* @param {number} spanStart inclusive lower bound (ms)
|
|
13
|
+
* @param {number} spanEnd inclusive upper bound (ms)
|
|
14
|
+
* @returns {number} unioned, clipped duration in ms (>= 0)
|
|
15
|
+
*/
|
|
16
|
+
export function clampedUnionMs(utts: Array<{
|
|
17
|
+
start?: number;
|
|
18
|
+
end?: number;
|
|
19
|
+
}>, spanStart: number, spanEnd: number): number;
|
|
20
|
+
/**
|
|
21
|
+
* Judge whether a diarisation timeline is reliable enough to label segments.
|
|
22
|
+
*
|
|
23
|
+
* @param {Array<{start:number,end:number,speaker:string}>} utterances
|
|
24
|
+
* diarisation timeline in milliseconds (AssemblyAI `utterances` shape).
|
|
25
|
+
* @param {Array<{start_ms:number,end_ms:number}>} segments
|
|
26
|
+
* transcript segments in milliseconds (used to measure coverage).
|
|
27
|
+
* @param {Partial<typeof DIARISATION_GATE_DEFAULTS>} [opts] threshold overrides.
|
|
28
|
+
* @returns {{ reliable: boolean, speakerCount: number, coverage: number,
|
|
29
|
+
* durationMs: number, reason: string }}
|
|
30
|
+
*/
|
|
31
|
+
export function assessDiarisation(utterances: Array<{
|
|
32
|
+
start: number;
|
|
33
|
+
end: number;
|
|
34
|
+
speaker: string;
|
|
35
|
+
}>, segments: Array<{
|
|
36
|
+
start_ms: number;
|
|
37
|
+
end_ms: number;
|
|
38
|
+
}>, opts?: Partial<typeof DIARISATION_GATE_DEFAULTS>): {
|
|
39
|
+
reliable: boolean;
|
|
40
|
+
speakerCount: number;
|
|
41
|
+
coverage: number;
|
|
42
|
+
durationMs: number;
|
|
43
|
+
reason: string;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Map acoustic diarisation speakers ("Speaker 1/2/3") to REAL names from a
|
|
47
|
+
* labelled reference timeline (platform transcript / roster), by greatest
|
|
48
|
+
* total time-overlap. Upgrades anonymous acoustic labels to real names when a
|
|
49
|
+
* labelled source exists (speaker-ID ladder rung A over C). Pure.
|
|
50
|
+
*
|
|
51
|
+
* Only confident matches are returned: the winning name must cover
|
|
52
|
+
* >= minOverlapFraction of the acoustic speaker's speaking time AND beat the
|
|
53
|
+
* runner-up by >= dominanceRatio. Ambiguous / unmatched acoustic speakers are
|
|
54
|
+
* listed in `unmapped`, so the caller keeps their anonymous label.
|
|
55
|
+
*
|
|
56
|
+
* Note: two acoustic speakers can map to the same real name (acoustic over-
|
|
57
|
+
* split one person). That is acceptable - downstream can merge. 1:1 dedup is a
|
|
58
|
+
* future refinement.
|
|
59
|
+
*
|
|
60
|
+
* @param {Array<{start:number,end:number,speaker:string}>} acoustic utterances (ms)
|
|
61
|
+
* @param {Array<{start:number,end:number,name:string}>} labelled real-name turns (ms)
|
|
62
|
+
* @param {Partial<typeof SPEAKER_MAP_DEFAULTS>} [opts]
|
|
63
|
+
* @returns {{ map: Record<string,string>, unmapped: string[] }}
|
|
64
|
+
*/
|
|
65
|
+
export function mapSpeakersToNames(acoustic: Array<{
|
|
66
|
+
start: number;
|
|
67
|
+
end: number;
|
|
68
|
+
speaker: string;
|
|
69
|
+
}>, labelled: Array<{
|
|
70
|
+
start: number;
|
|
71
|
+
end: number;
|
|
72
|
+
name: string;
|
|
73
|
+
}>, opts?: Partial<typeof SPEAKER_MAP_DEFAULTS>): {
|
|
74
|
+
map: Record<string, string>;
|
|
75
|
+
unmapped: string[];
|
|
76
|
+
};
|
|
77
|
+
export namespace DIARISATION_GATE_DEFAULTS {
|
|
78
|
+
let soleSpeakerSuspectAboveMs: number;
|
|
79
|
+
let minCoverage: number;
|
|
80
|
+
let trustAtSpeakers: number;
|
|
81
|
+
}
|
|
82
|
+
export namespace SPEAKER_MAP_DEFAULTS {
|
|
83
|
+
let minOverlapFraction: number;
|
|
84
|
+
let dominanceRatio: number;
|
|
85
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check a single word against the supplied dictionary + accept-list.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} word - The word to check (no punctuation, single token).
|
|
5
|
+
* @param {object} opts
|
|
6
|
+
* @param {object} opts.nspell - An nspell instance (server side, built
|
|
7
|
+
* from dictionary-cy or dictionary-en-gb).
|
|
8
|
+
* @param {Set<string>} [opts.accept] - Lowercased proper nouns + glossary
|
|
9
|
+
* + word_boost_approved terms that
|
|
10
|
+
* must never be flagged.
|
|
11
|
+
* @param {boolean} [opts.welsh] - When true, additionally try mutation
|
|
12
|
+
* reversal before declaring the word
|
|
13
|
+
* misspelled. Pass false for English.
|
|
14
|
+
* @returns {boolean} true if the word is considered correctly spelled.
|
|
15
|
+
*/
|
|
16
|
+
export function checkWord(word: string, { nspell, accept, welsh }: {
|
|
17
|
+
nspell: object;
|
|
18
|
+
accept?: Set<string>;
|
|
19
|
+
welsh?: boolean;
|
|
20
|
+
}): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Tokenise a string into checkable words + their positions, so a UI
|
|
23
|
+
* can underline only the wrong tokens without re-tokenising itself.
|
|
24
|
+
*
|
|
25
|
+
* Position is the offset in the original string. Tokens are word-like
|
|
26
|
+
* runs of letters/marks/apostrophes/hyphens; punctuation is skipped.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} text
|
|
29
|
+
* @returns {{ word: string, start: number, end: number }[]}
|
|
30
|
+
*/
|
|
31
|
+
export function tokenise(text: string): {
|
|
32
|
+
word: string;
|
|
33
|
+
start: number;
|
|
34
|
+
end: number;
|
|
35
|
+
}[];
|
|
36
|
+
/**
|
|
37
|
+
* Check an entire string and return the misspelled tokens (if any).
|
|
38
|
+
* Convenience wrapper around tokenise + checkWord.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} text
|
|
41
|
+
* @param {object} opts - same shape as checkWord opts
|
|
42
|
+
* @param {object} opts.nspell - An nspell instance.
|
|
43
|
+
* @param {Set<string>} [opts.accept] - Terms that must never be flagged.
|
|
44
|
+
* @param {boolean} [opts.welsh] - Try mutation reversal before flagging.
|
|
45
|
+
* @returns {{ word: string, start: number, end: number }[]}
|
|
46
|
+
*/
|
|
47
|
+
export function checkSentence(text: string, opts: {
|
|
48
|
+
nspell: object;
|
|
49
|
+
accept?: Set<string>;
|
|
50
|
+
welsh?: boolean;
|
|
51
|
+
}): {
|
|
52
|
+
word: string;
|
|
53
|
+
start: number;
|
|
54
|
+
end: number;
|
|
55
|
+
}[];
|
|
56
|
+
/**
|
|
57
|
+
* Generate up to N suggestions for a misspelled word. Wraps
|
|
58
|
+
* nspell.suggest() but biases toward `accept` (proper nouns are
|
|
59
|
+
* surfaced first when they're close in edit distance).
|
|
60
|
+
*
|
|
61
|
+
* @param {string} word
|
|
62
|
+
* @param {object} opts
|
|
63
|
+
* @param {object} opts.nspell
|
|
64
|
+
* @param {Set<string>} [opts.accept]
|
|
65
|
+
* @param {boolean} [opts.welsh] - Bias suggestions for Welsh mutation forms.
|
|
66
|
+
* @param {number} [opts.max] - default 6
|
|
67
|
+
* @returns {string[]}
|
|
68
|
+
*/
|
|
69
|
+
export function suggest(word: string, { nspell, accept, welsh, max }: {
|
|
70
|
+
nspell: object;
|
|
71
|
+
accept?: Set<string>;
|
|
72
|
+
welsh?: boolean;
|
|
73
|
+
max?: number;
|
|
74
|
+
}): string[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { checkWord, checkSentence, tokenise, suggest } from "./core.js";
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Split a Welsh word into its constituent letters, treating digraphs
|
|
3
|
+
* as single units. Case-preserving.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* splitIntoLetters('llwybr') // ['ll', 'w', 'y', 'b', 'r']
|
|
7
|
+
* splitIntoLetters('CHWARAE') // ['CH', 'W', 'A', 'R', 'A', 'E']
|
|
8
|
+
* splitIntoLetters('rhag-') // ['rh', 'a', 'g', '-']
|
|
9
|
+
*
|
|
10
|
+
* @param {string} word
|
|
11
|
+
* @returns {string[]}
|
|
12
|
+
*/
|
|
13
|
+
export function splitIntoLetters(word: string): string[];
|
|
14
|
+
/**
|
|
15
|
+
* Welsh-aware character count: a digraph counts as ONE letter.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* welshLength('llwybr') // 5 (ll-w-y-b-r), NOT 6
|
|
19
|
+
* welshLength('chwarae') // 6 (ch-w-a-r-a-e)
|
|
20
|
+
* welshLength('Eisteddfod') // 9 (e-i-s-t-e-dd-f-o-d), NOT 10
|
|
21
|
+
*
|
|
22
|
+
* @param {string} word
|
|
23
|
+
* @returns {number}
|
|
24
|
+
*/
|
|
25
|
+
export function welshLength(word: string): number;
|
|
26
|
+
/**
|
|
27
|
+
* Reverse a Welsh word preserving digraphs (used by anagram /
|
|
28
|
+
* caption-style transforms that mirror text).
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* welshReverse('llwybr') // 'rbywll' (NOT 'rbywll' broken to 'rbyw ll')
|
|
32
|
+
*
|
|
33
|
+
* @param {string} word
|
|
34
|
+
* @returns {string}
|
|
35
|
+
*/
|
|
36
|
+
export function welshReverse(word: string): string;
|
|
37
|
+
export function welshCompare(a: any, b: any): number;
|
|
38
|
+
/**
|
|
39
|
+
* Check if a string position lies on a digraph boundary. Useful for
|
|
40
|
+
* cursor / selection logic: when the cursor sits after `l` in `llwybr`,
|
|
41
|
+
* a single backspace should remove BOTH letters of `ll`, not just one.
|
|
42
|
+
*
|
|
43
|
+
* Returns `true` if the character at `index` is the second half of a
|
|
44
|
+
* digraph (i.e. the digraph started at `index - 1`).
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* isDigraphContinuation('llwybr', 1) // true ('l' completing 'll')
|
|
48
|
+
* isDigraphContinuation('llwybr', 2) // false ('w')
|
|
49
|
+
* isDigraphContinuation('cath', 1) // false ('a')
|
|
50
|
+
*
|
|
51
|
+
* @param {string} word
|
|
52
|
+
* @param {number} index
|
|
53
|
+
* @returns {boolean}
|
|
54
|
+
*/
|
|
55
|
+
export function isDigraphContinuation(word: string, index: number): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* @capsiynau/intelligence/welsh/digraphs
|
|
58
|
+
*
|
|
59
|
+
* Welsh digraph handling. In the Welsh alphabet, these eight digraphs
|
|
60
|
+
* are SINGLE letters and must be treated as one unit for:
|
|
61
|
+
* - character iteration
|
|
62
|
+
* - cursor movement
|
|
63
|
+
* - word length
|
|
64
|
+
* - alphabetical sort
|
|
65
|
+
* - reversal (for anagrams etc.)
|
|
66
|
+
*
|
|
67
|
+
* The eight digraphs (canonical order in the Welsh alphabet):
|
|
68
|
+
* ch dd ff ng ll ph rh th
|
|
69
|
+
*
|
|
70
|
+
* Note: `ng` is the most context-sensitive. In some words it represents
|
|
71
|
+
* a single sound (canu's "ng" is /ŋ/) but in compound words it can be
|
|
72
|
+
* `n` + `g` separately (e.g. "Bangor" is BAN+GOR phonetically but
|
|
73
|
+
* conventionally still written without splitting). For purely
|
|
74
|
+
* orthographic operations (length, indexing, anagrams), we treat every
|
|
75
|
+
* occurrence as a single digraph. Callers needing phonetic accuracy
|
|
76
|
+
* must layer their own lexicon lookup on top.
|
|
77
|
+
*
|
|
78
|
+
* Pure JS, no external deps.
|
|
79
|
+
*/
|
|
80
|
+
/**
|
|
81
|
+
* The eight Welsh digraphs in their canonical alphabetical order.
|
|
82
|
+
* @type {readonly string[]}
|
|
83
|
+
*/
|
|
84
|
+
export const WELSH_DIGRAPHS: readonly string[];
|
|
85
|
+
/**
|
|
86
|
+
* Map of digraph -> alphabetical position (0-indexed within the digraph set).
|
|
87
|
+
* For full Welsh-alphabet collation, layer this on top of the single-letter
|
|
88
|
+
* position map in `WELSH_ALPHABET_ORDER` below.
|
|
89
|
+
*/
|
|
90
|
+
export const DIGRAPH_INDEX: Readonly<{
|
|
91
|
+
[k: string]: number;
|
|
92
|
+
}>;
|
|
93
|
+
/**
|
|
94
|
+
* The canonical Welsh alphabet in collation order. Note the digraphs
|
|
95
|
+
* follow their leading letter: `c` then `ch`, `d` then `dd`, etc.
|
|
96
|
+
* Letters not in Welsh (`k`, `q`, `v`, `x`, `z`) are omitted; loan words
|
|
97
|
+
* containing them are conventionally sorted as if they were English.
|
|
98
|
+
*
|
|
99
|
+
* Source: Geiriadur Prifysgol Cymru collation rules.
|
|
100
|
+
* @type {readonly string[]}
|
|
101
|
+
*/
|
|
102
|
+
export const WELSH_ALPHABET_ORDER: readonly string[];
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apply a mutation to a Welsh word. Returns the mutated form.
|
|
3
|
+
* Preserves the case of the first character.
|
|
4
|
+
*
|
|
5
|
+
* Soft mutation of `g` is DELETION — the word loses its initial `g`.
|
|
6
|
+
* e.g. 'gardd' -> soft -> 'ardd' (the garden, after 'yr' for feminine).
|
|
7
|
+
*
|
|
8
|
+
* If the word's initial consonant is not subject to the given mutation,
|
|
9
|
+
* returns the word unchanged.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} word
|
|
12
|
+
* @param {'soft'|'nasal'|'aspirate'} kind
|
|
13
|
+
* @returns {string}
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* applyMutation('cath', 'soft') // 'gath'
|
|
17
|
+
* applyMutation('Tad', 'soft') // 'Dad' (case preserved)
|
|
18
|
+
* applyMutation('ci', 'nasal') // 'nghi'
|
|
19
|
+
* applyMutation('Pen', 'aspirate') // 'Phen'
|
|
20
|
+
* applyMutation('gardd', 'soft') // 'ardd' (g-deletion)
|
|
21
|
+
* applyMutation('llaw', 'soft') // 'law'
|
|
22
|
+
* applyMutation('siop', 'soft') // 'siop' (s not subject)
|
|
23
|
+
*/
|
|
24
|
+
export function applyMutation(word: string, kind: "soft" | "nasal" | "aspirate"): string;
|
|
25
|
+
/**
|
|
26
|
+
* Reverse a mutation: given an observed (possibly mutated) word,
|
|
27
|
+
* return all candidate root forms. The caller can intersect this
|
|
28
|
+
* with a dictionary lookup to pick the real root.
|
|
29
|
+
*
|
|
30
|
+
* If `kind` is provided, only candidates from that mutation are
|
|
31
|
+
* returned. If omitted, candidates from all three mutations.
|
|
32
|
+
*
|
|
33
|
+
* The observed word itself is ALWAYS included as the first candidate
|
|
34
|
+
* (it might not actually be mutated).
|
|
35
|
+
*
|
|
36
|
+
* @param {string} word
|
|
37
|
+
* @param {'soft'|'nasal'|'aspirate'} [kind]
|
|
38
|
+
* @returns {{ root: string, kind: string | null }[]}
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* possibleRoots('gath') // [
|
|
42
|
+
* // { root: 'gath', kind: null }, // as-is
|
|
43
|
+
* // { root: 'cath', kind: 'soft' }, // c -> g
|
|
44
|
+
* // ]
|
|
45
|
+
* possibleRoots('phen', 'aspirate') // [
|
|
46
|
+
* // { root: 'phen', kind: null },
|
|
47
|
+
* // { root: 'pen', kind: 'aspirate' },
|
|
48
|
+
* // ]
|
|
49
|
+
*/
|
|
50
|
+
export function possibleRoots(word: string, kind?: "soft" | "nasal" | "aspirate"): {
|
|
51
|
+
root: string;
|
|
52
|
+
kind: string | null;
|
|
53
|
+
}[];
|
|
54
|
+
/**
|
|
55
|
+
* Best-effort classification: does this word LOOK mutated?
|
|
56
|
+
*
|
|
57
|
+
* Returns the mutation kind if the word starts with a sequence
|
|
58
|
+
* characteristic of one specific mutation (e.g. 'ph' / 'th' / 'ch' are
|
|
59
|
+
* aspirate-only; 'mh' / 'nh' / 'ngh' are nasal-only). Returns null if
|
|
60
|
+
* the word either isn't mutated or its surface form is ambiguous.
|
|
61
|
+
*
|
|
62
|
+
* NOT a reliable mutation detector on its own. Use with caller context.
|
|
63
|
+
*
|
|
64
|
+
* @param {string} word
|
|
65
|
+
* @returns {'soft'|'nasal'|'aspirate'|null}
|
|
66
|
+
*/
|
|
67
|
+
export function classifyMutation(word: string): "soft" | "nasal" | "aspirate" | null;
|
|
68
|
+
/**
|
|
69
|
+
* The three mutation kinds as a const enum-ish object.
|
|
70
|
+
* @type {Readonly<{soft:'soft', nasal:'nasal', aspirate:'aspirate'}>}
|
|
71
|
+
*/
|
|
72
|
+
export const MUTATION: Readonly<{
|
|
73
|
+
soft: "soft";
|
|
74
|
+
nasal: "nasal";
|
|
75
|
+
aspirate: "aspirate";
|
|
76
|
+
}>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalise raw Welsh/English transcript text.
|
|
3
|
+
* Repairs broken contractions, fixes encoding, removes zero-width chars.
|
|
4
|
+
* Safe to apply to English too — repairs are Welsh-specific patterns unlikely
|
|
5
|
+
* to match English text.
|
|
6
|
+
*/
|
|
7
|
+
export function normaliseWelsh(text: any): any;
|
|
8
|
+
/**
|
|
9
|
+
* Get a dialect-specific system prompt fragment for AI calls.
|
|
10
|
+
* @param {'broadcast'|'north'|'south'|'formal'} dialect
|
|
11
|
+
* @returns {string}
|
|
12
|
+
*/
|
|
13
|
+
export function getDialectSystemPrompt(dialect: "broadcast" | "north" | "south" | "formal"): string;
|