@willwade/aac-processors 0.1.7 → 0.1.9
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/dist/analytics.d.ts +7 -0
- package/dist/analytics.js +23 -0
- package/dist/browser/index.browser.js +5 -0
- package/dist/browser/metrics.js +17 -0
- package/dist/browser/processors/gridset/helpers.js +390 -0
- package/dist/browser/processors/gridset/pluginTypes.js +1 -0
- package/dist/browser/processors/gridsetProcessor.js +68 -1
- package/dist/browser/processors/obfProcessor.js +21 -13
- package/dist/browser/processors/snap/helpers.js +252 -0
- package/dist/browser/utilities/analytics/history.js +116 -0
- package/dist/browser/utilities/analytics/metrics/comparison.js +477 -0
- package/dist/browser/utilities/analytics/metrics/core.js +775 -0
- package/dist/browser/utilities/analytics/metrics/effort.js +221 -0
- package/dist/browser/utilities/analytics/metrics/obl-types.js +6 -0
- package/dist/browser/utilities/analytics/metrics/obl.js +282 -0
- package/dist/browser/utilities/analytics/metrics/sentence.js +121 -0
- package/dist/browser/utilities/analytics/metrics/types.js +6 -0
- package/dist/browser/utilities/analytics/metrics/vocabulary.js +138 -0
- package/dist/browser/utilities/analytics/reference/browser.js +67 -0
- package/dist/browser/utilities/analytics/reference/index.js +129 -0
- package/dist/browser/utils/dotnetTicks.js +17 -0
- package/dist/index.browser.d.ts +1 -0
- package/dist/index.browser.js +18 -1
- package/dist/index.node.d.ts +2 -2
- package/dist/index.node.js +5 -5
- package/dist/metrics.d.ts +17 -0
- package/dist/metrics.js +44 -0
- package/dist/processors/gridset/pluginTypes.d.ts +1 -0
- package/dist/processors/gridset/pluginTypes.js +1 -0
- package/dist/processors/gridsetProcessor.js +68 -1
- package/dist/processors/obfProcessor.js +21 -13
- package/dist/utilities/analytics/metrics/comparison.d.ts +2 -1
- package/dist/utilities/analytics/metrics/comparison.js +3 -3
- package/dist/utilities/analytics/metrics/vocabulary.d.ts +2 -2
- package/dist/utilities/analytics/reference/browser.d.ts +31 -0
- package/dist/utilities/analytics/reference/browser.js +73 -0
- package/dist/utilities/analytics/reference/index.d.ts +21 -0
- package/dist/utilities/analytics/reference/index.js +22 -46
- package/package.json +9 -1
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vocabulary Coverage Analysis
|
|
3
|
+
*
|
|
4
|
+
* Analyzes how well an AAC board set covers core vocabulary
|
|
5
|
+
* and identifies missing/extra words compared to reference lists.
|
|
6
|
+
*/
|
|
7
|
+
import { ReferenceLoader } from '../reference/index';
|
|
8
|
+
import { spellingEffort } from './effort';
|
|
9
|
+
export class VocabularyAnalyzer {
|
|
10
|
+
constructor(referenceLoader) {
|
|
11
|
+
this.referenceLoader = referenceLoader || new ReferenceLoader();
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Analyze vocabulary coverage against core lists
|
|
15
|
+
*/
|
|
16
|
+
analyze(metrics, options) {
|
|
17
|
+
// const locale = options?.locale || metrics.locale || 'en';
|
|
18
|
+
const highEffortThreshold = options?.highEffortThreshold || 5.0;
|
|
19
|
+
const lowEffortThreshold = options?.lowEffortThreshold || 2.0;
|
|
20
|
+
// Load reference data
|
|
21
|
+
const coreLists = this.referenceLoader.loadCoreLists();
|
|
22
|
+
// Create word to effort map (using lowercase keys for matching)
|
|
23
|
+
const wordEffortMap = new Map();
|
|
24
|
+
metrics.buttons.forEach((btn) => {
|
|
25
|
+
const word = btn.label.toLowerCase();
|
|
26
|
+
const existing = wordEffortMap.get(word);
|
|
27
|
+
if (!existing || btn.effort < existing) {
|
|
28
|
+
wordEffortMap.set(word, btn.effort);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
// Analyze each core list
|
|
32
|
+
const core_coverage = {};
|
|
33
|
+
coreLists.forEach((list) => {
|
|
34
|
+
const analysis = this.analyzeCoreList(list, wordEffortMap);
|
|
35
|
+
core_coverage[list.id] = analysis;
|
|
36
|
+
});
|
|
37
|
+
// Find extra words (words not in any core list)
|
|
38
|
+
const allCoreWords = new Set();
|
|
39
|
+
coreLists.forEach((list) => {
|
|
40
|
+
list.words.forEach((word) => allCoreWords.add(word.toLowerCase()));
|
|
41
|
+
});
|
|
42
|
+
const extraWords = [];
|
|
43
|
+
wordEffortMap.forEach((effort, word) => {
|
|
44
|
+
if (!allCoreWords.has(word.toLowerCase())) {
|
|
45
|
+
extraWords.push(word);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
extraWords.sort((a, b) => a.localeCompare(b));
|
|
49
|
+
// Find high/low effort words
|
|
50
|
+
const highEffortWords = [];
|
|
51
|
+
const lowEffortWords = [];
|
|
52
|
+
wordEffortMap.forEach((effort, word) => {
|
|
53
|
+
if (effort > highEffortThreshold) {
|
|
54
|
+
highEffortWords.push({ word, effort });
|
|
55
|
+
}
|
|
56
|
+
else if (effort < lowEffortThreshold) {
|
|
57
|
+
lowEffortWords.push({ word, effort });
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
highEffortWords.sort((a, b) => b.effort - a.effort);
|
|
61
|
+
lowEffortWords.sort((a, b) => a.effort - b.effort);
|
|
62
|
+
return {
|
|
63
|
+
core_coverage,
|
|
64
|
+
total_unique_words: wordEffortMap.size,
|
|
65
|
+
words_with_effort: wordEffortMap.size,
|
|
66
|
+
words_requiring_spelling: 0, // Calculated during sentence analysis
|
|
67
|
+
extra_words: extraWords,
|
|
68
|
+
high_effort_words: highEffortWords.slice(0, 50), // Top 50
|
|
69
|
+
low_effort_words: lowEffortWords.slice(0, 50), // Bottom 50
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Analyze coverage for a single core list
|
|
74
|
+
*/
|
|
75
|
+
analyzeCoreList(list, wordEffortMap) {
|
|
76
|
+
const covered = [];
|
|
77
|
+
const missing = [];
|
|
78
|
+
let totalEffort = 0;
|
|
79
|
+
list.words.forEach((word) => {
|
|
80
|
+
const lowerWord = word.toLowerCase();
|
|
81
|
+
const effort = wordEffortMap.get(lowerWord);
|
|
82
|
+
if (effort !== undefined) {
|
|
83
|
+
covered.push(word);
|
|
84
|
+
totalEffort += effort;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
missing.push(word);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
const averageEffort = covered.length > 0 ? totalEffort / covered.length : 0;
|
|
91
|
+
return {
|
|
92
|
+
name: list.name,
|
|
93
|
+
total_words: list.words.length,
|
|
94
|
+
covered: covered.length,
|
|
95
|
+
missing: missing.length,
|
|
96
|
+
coverage_percent: (covered.length / list.words.length) * 100,
|
|
97
|
+
missing_words: missing,
|
|
98
|
+
average_effort: averageEffort,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Calculate coverage percentage for a specific word list
|
|
103
|
+
*/
|
|
104
|
+
calculateCoverage(wordList, metrics) {
|
|
105
|
+
const wordSet = new Set(metrics.buttons.map((btn) => btn.label.toLowerCase()));
|
|
106
|
+
const covered = [];
|
|
107
|
+
const missing = [];
|
|
108
|
+
wordList.forEach((word) => {
|
|
109
|
+
if (wordSet.has(word.toLowerCase())) {
|
|
110
|
+
covered.push(word);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
missing.push(word);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
return {
|
|
117
|
+
covered,
|
|
118
|
+
missing,
|
|
119
|
+
coverage_percent: (covered.length / wordList.length) * 100,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Get effort for a word, or calculate spelling effort if missing
|
|
124
|
+
*/
|
|
125
|
+
getWordEffort(word, metrics) {
|
|
126
|
+
const btn = metrics.buttons.find((b) => b.label.toLowerCase() === word.toLowerCase());
|
|
127
|
+
if (btn) {
|
|
128
|
+
return btn.effort;
|
|
129
|
+
}
|
|
130
|
+
return spellingEffort(word, metrics.spelling_effort_base, metrics.spelling_effort_per_letter);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Check if a word is in the board set
|
|
134
|
+
*/
|
|
135
|
+
hasWord(word, metrics) {
|
|
136
|
+
return metrics.buttons.some((b) => b.label.toLowerCase() === word.toLowerCase());
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-friendly reference data loader using fetch.
|
|
3
|
+
*/
|
|
4
|
+
export class InMemoryReferenceLoader {
|
|
5
|
+
constructor(data) {
|
|
6
|
+
this.data = data;
|
|
7
|
+
}
|
|
8
|
+
loadCoreLists() {
|
|
9
|
+
return this.data.coreLists;
|
|
10
|
+
}
|
|
11
|
+
loadCommonWords() {
|
|
12
|
+
return this.data.commonWords;
|
|
13
|
+
}
|
|
14
|
+
loadSynonyms() {
|
|
15
|
+
return this.data.synonyms;
|
|
16
|
+
}
|
|
17
|
+
loadSentences() {
|
|
18
|
+
return this.data.sentences;
|
|
19
|
+
}
|
|
20
|
+
loadFringe() {
|
|
21
|
+
return this.data.fringe;
|
|
22
|
+
}
|
|
23
|
+
loadBaseWords() {
|
|
24
|
+
return this.data.baseWords;
|
|
25
|
+
}
|
|
26
|
+
loadCommonFringe() {
|
|
27
|
+
const commonWords = new Set(this.data.commonWords.words.map((w) => w.toLowerCase()));
|
|
28
|
+
const coreWords = new Set();
|
|
29
|
+
this.data.coreLists.forEach((list) => {
|
|
30
|
+
list.words.forEach((word) => coreWords.add(word.toLowerCase()));
|
|
31
|
+
});
|
|
32
|
+
return Array.from(commonWords).filter((word) => !coreWords.has(word));
|
|
33
|
+
}
|
|
34
|
+
loadAll() {
|
|
35
|
+
return this.data;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export async function loadReferenceDataFromUrl(baseUrl, locale = 'en') {
|
|
39
|
+
const root = baseUrl.replace(/\/$/, '');
|
|
40
|
+
const fetchJson = async (name) => {
|
|
41
|
+
const res = await fetch(`${root}/${name}.${locale}.json`);
|
|
42
|
+
if (!res.ok) {
|
|
43
|
+
throw new Error(`Failed to load ${name}.${locale}.json`);
|
|
44
|
+
}
|
|
45
|
+
return (await res.json());
|
|
46
|
+
};
|
|
47
|
+
const [coreLists, commonWords, synonyms, sentences, fringe, baseWords] = await Promise.all([
|
|
48
|
+
fetchJson('core_lists'),
|
|
49
|
+
fetchJson('common_words'),
|
|
50
|
+
fetchJson('synonyms'),
|
|
51
|
+
fetchJson('sentences'),
|
|
52
|
+
fetchJson('fringe'),
|
|
53
|
+
fetchJson('base_words'),
|
|
54
|
+
]);
|
|
55
|
+
return {
|
|
56
|
+
coreLists,
|
|
57
|
+
commonWords,
|
|
58
|
+
synonyms,
|
|
59
|
+
sentences,
|
|
60
|
+
fringe,
|
|
61
|
+
baseWords,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export async function createBrowserReferenceLoader(baseUrl, locale = 'en') {
|
|
65
|
+
const data = await loadReferenceDataFromUrl(baseUrl, locale);
|
|
66
|
+
return new InMemoryReferenceLoader(data);
|
|
67
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reference Data Loader
|
|
3
|
+
*
|
|
4
|
+
* Loads reference vocabulary lists, core lists, and sentences
|
|
5
|
+
* for AAC metrics analysis.
|
|
6
|
+
*/
|
|
7
|
+
import { getFs, getPath } from '../../../utils/io';
|
|
8
|
+
export class ReferenceLoader {
|
|
9
|
+
constructor(dataDir, locale = 'en') {
|
|
10
|
+
this.locale = locale;
|
|
11
|
+
if (dataDir) {
|
|
12
|
+
this.dataDir = dataDir;
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
// Resolve the data directory relative to this file's location
|
|
16
|
+
// Use __dirname which works correctly after compilation
|
|
17
|
+
this.dataDir = getPath().join(__dirname, 'data');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Load core vocabulary lists
|
|
22
|
+
*/
|
|
23
|
+
loadCoreLists() {
|
|
24
|
+
const filePath = getPath().join(this.dataDir, `core_lists.${this.locale}.json`);
|
|
25
|
+
const content = getFs().readFileSync(filePath, 'utf-8');
|
|
26
|
+
return JSON.parse(String(content));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Load common words with baseline effort scores
|
|
30
|
+
*/
|
|
31
|
+
loadCommonWords() {
|
|
32
|
+
const filePath = getPath().join(this.dataDir, `common_words.${this.locale}.json`);
|
|
33
|
+
const content = getFs().readFileSync(filePath, 'utf-8');
|
|
34
|
+
return JSON.parse(String(content));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Load synonym mappings
|
|
38
|
+
*/
|
|
39
|
+
loadSynonyms() {
|
|
40
|
+
const filePath = getPath().join(this.dataDir, `synonyms.${this.locale}.json`);
|
|
41
|
+
const content = getFs().readFileSync(filePath, 'utf-8');
|
|
42
|
+
return JSON.parse(String(content));
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Load test sentences
|
|
46
|
+
*/
|
|
47
|
+
loadSentences() {
|
|
48
|
+
const filePath = getPath().join(this.dataDir, `sentences.${this.locale}.json`);
|
|
49
|
+
const content = getFs().readFileSync(filePath, 'utf-8');
|
|
50
|
+
return JSON.parse(String(content));
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Load fringe vocabulary
|
|
54
|
+
*/
|
|
55
|
+
loadFringe() {
|
|
56
|
+
const filePath = getPath().join(this.dataDir, `fringe.${this.locale}.json`);
|
|
57
|
+
const content = getFs().readFileSync(filePath, 'utf-8');
|
|
58
|
+
const data = JSON.parse(String(content));
|
|
59
|
+
// Flatten nested category words if needed
|
|
60
|
+
if (Array.isArray(data) && data.length > 0 && data[0].categories) {
|
|
61
|
+
const flattened = [];
|
|
62
|
+
data.forEach((list) => {
|
|
63
|
+
list.categories.forEach((cat) => {
|
|
64
|
+
flattened.push(...cat.words);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
return flattened;
|
|
68
|
+
}
|
|
69
|
+
return data;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Load base words hash map
|
|
73
|
+
*/
|
|
74
|
+
loadBaseWords() {
|
|
75
|
+
const filePath = getPath().join(this.dataDir, `base_words.${this.locale}.json`);
|
|
76
|
+
const content = getFs().readFileSync(filePath, 'utf-8');
|
|
77
|
+
return JSON.parse(String(content));
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Load common fringe vocabulary
|
|
81
|
+
* Common words that are NOT in core vocabulary lists
|
|
82
|
+
* (matching Ruby loader.rb:413-420)
|
|
83
|
+
*/
|
|
84
|
+
loadCommonFringe() {
|
|
85
|
+
const commonWordsData = this.loadCommonWords();
|
|
86
|
+
const commonWords = new Set(commonWordsData.words.map((w) => w.toLowerCase()));
|
|
87
|
+
const coreLists = this.loadCoreLists();
|
|
88
|
+
const coreWords = new Set();
|
|
89
|
+
coreLists.forEach((list) => {
|
|
90
|
+
list.words.forEach((word) => coreWords.add(word.toLowerCase()));
|
|
91
|
+
});
|
|
92
|
+
// Common fringe = common words - core words
|
|
93
|
+
const commonFringe = Array.from(commonWords).filter((word) => !coreWords.has(word));
|
|
94
|
+
return commonFringe;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Get all reference data at once
|
|
98
|
+
*/
|
|
99
|
+
loadAll() {
|
|
100
|
+
return {
|
|
101
|
+
coreLists: this.loadCoreLists(),
|
|
102
|
+
commonWords: this.loadCommonWords(),
|
|
103
|
+
synonyms: this.loadSynonyms(),
|
|
104
|
+
sentences: this.loadSentences(),
|
|
105
|
+
fringe: this.loadFringe(),
|
|
106
|
+
baseWords: this.loadBaseWords(),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get the default reference data path
|
|
112
|
+
*/
|
|
113
|
+
export function getReferenceDataPath() {
|
|
114
|
+
return String(getPath().join(__dirname, 'data'));
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Check if reference data files exist
|
|
118
|
+
*/
|
|
119
|
+
export function hasReferenceData() {
|
|
120
|
+
const dataPath = getReferenceDataPath();
|
|
121
|
+
const requiredFiles = [
|
|
122
|
+
'core_lists.en.json',
|
|
123
|
+
'common_words.en.json',
|
|
124
|
+
'sentences.en.json',
|
|
125
|
+
'synonyms.en.json',
|
|
126
|
+
'fringe.en.json',
|
|
127
|
+
];
|
|
128
|
+
return requiredFiles.every((file) => getFs().existsSync(getPath().join(dataPath, file)));
|
|
129
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Number of ticks (.NET 100ns units) between 0001-01-01 and Unix epoch.
|
|
3
|
+
*/
|
|
4
|
+
export const DOTNET_EPOCH_TICKS = 621355968000000000n;
|
|
5
|
+
/**
|
|
6
|
+
* Number of ticks per millisecond.
|
|
7
|
+
*/
|
|
8
|
+
export const TICKS_PER_MILLISECOND = 10000n;
|
|
9
|
+
/**
|
|
10
|
+
* Convert .NET ticks (100ns since 0001-01-01) to a JavaScript Date.
|
|
11
|
+
* Accepts bigint or number and rounds down to millisecond precision.
|
|
12
|
+
*/
|
|
13
|
+
export function dotNetTicksToDate(ticks) {
|
|
14
|
+
const tickValue = BigInt(ticks);
|
|
15
|
+
const ms = Number((tickValue - DOTNET_EPOCH_TICKS) / TICKS_PER_MILLISECOND);
|
|
16
|
+
return new Date(ms);
|
|
17
|
+
}
|
package/dist/index.browser.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export { SnapProcessor } from './processors/snapProcessor';
|
|
|
22
22
|
export { TouchChatProcessor } from './processors/touchchatProcessor';
|
|
23
23
|
export { ApplePanelsProcessor } from './processors/applePanelsProcessor';
|
|
24
24
|
export { AstericsGridProcessor } from './processors/astericsGridProcessor';
|
|
25
|
+
export * as Metrics from './metrics';
|
|
25
26
|
import { BaseProcessor } from './core/baseProcessor';
|
|
26
27
|
export { configureSqlJs } from './utils/sqlite';
|
|
27
28
|
/**
|
package/dist/index.browser.js
CHANGED
|
@@ -23,11 +23,23 @@ var __createBinding = (this && this.__createBinding) || (Object.create ? (functi
|
|
|
23
23
|
if (k2 === undefined) k2 = k;
|
|
24
24
|
o[k2] = m[k];
|
|
25
25
|
}));
|
|
26
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
27
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
28
|
+
}) : function(o, v) {
|
|
29
|
+
o["default"] = v;
|
|
30
|
+
});
|
|
26
31
|
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
27
32
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
28
33
|
};
|
|
34
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
29
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
-
exports.configureSqlJs = exports.AstericsGridProcessor = exports.ApplePanelsProcessor = exports.TouchChatProcessor = exports.SnapProcessor = exports.GridsetProcessor = exports.ObfProcessor = exports.OpmlProcessor = exports.DotProcessor = void 0;
|
|
42
|
+
exports.configureSqlJs = exports.Metrics = exports.AstericsGridProcessor = exports.ApplePanelsProcessor = exports.TouchChatProcessor = exports.SnapProcessor = exports.GridsetProcessor = exports.ObfProcessor = exports.OpmlProcessor = exports.DotProcessor = void 0;
|
|
31
43
|
exports.getProcessor = getProcessor;
|
|
32
44
|
exports.getSupportedExtensions = getSupportedExtensions;
|
|
33
45
|
exports.isExtensionSupported = isExtensionSupported;
|
|
@@ -56,6 +68,11 @@ var applePanelsProcessor_1 = require("./processors/applePanelsProcessor");
|
|
|
56
68
|
Object.defineProperty(exports, "ApplePanelsProcessor", { enumerable: true, get: function () { return applePanelsProcessor_1.ApplePanelsProcessor; } });
|
|
57
69
|
var astericsGridProcessor_1 = require("./processors/astericsGridProcessor");
|
|
58
70
|
Object.defineProperty(exports, "AstericsGridProcessor", { enumerable: true, get: function () { return astericsGridProcessor_1.AstericsGridProcessor; } });
|
|
71
|
+
// ===================================================================
|
|
72
|
+
// UTILITY FUNCTIONS
|
|
73
|
+
// ===================================================================
|
|
74
|
+
// Metrics namespace (pageset analytics)
|
|
75
|
+
exports.Metrics = __importStar(require("./metrics"));
|
|
59
76
|
const dotProcessor_2 = require("./processors/dotProcessor");
|
|
60
77
|
const opmlProcessor_2 = require("./processors/opmlProcessor");
|
|
61
78
|
const obfProcessor_2 = require("./processors/obfProcessor");
|
package/dist/index.node.d.ts
CHANGED
|
@@ -9,9 +9,9 @@ export * from './core/treeStructure';
|
|
|
9
9
|
export * from './core/baseProcessor';
|
|
10
10
|
export * from './core/stringCasing';
|
|
11
11
|
export * from './processors';
|
|
12
|
-
export * as Analytics from './
|
|
13
|
-
export * from './utilities/analytics';
|
|
12
|
+
export * as Analytics from './analytics';
|
|
14
13
|
export * as Validation from './validation';
|
|
14
|
+
export * as Metrics from './metrics';
|
|
15
15
|
export * as Gridset from './gridset';
|
|
16
16
|
export * as Snap from './snap';
|
|
17
17
|
export * as OBF from './obf';
|
package/dist/index.node.js
CHANGED
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
33
33
|
return result;
|
|
34
34
|
};
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.Translation = exports.AstericsGrid = exports.ApplePanels = exports.Opml = exports.Excel = exports.Dot = exports.TouchChat = exports.Obfset = exports.OBF = exports.Snap = exports.Gridset = exports.Validation = exports.Analytics = void 0;
|
|
36
|
+
exports.Translation = exports.AstericsGrid = exports.ApplePanels = exports.Opml = exports.Excel = exports.Dot = exports.TouchChat = exports.Obfset = exports.OBF = exports.Snap = exports.Gridset = exports.Metrics = exports.Validation = exports.Analytics = void 0;
|
|
37
37
|
exports.getProcessor = getProcessor;
|
|
38
38
|
exports.getSupportedExtensions = getSupportedExtensions;
|
|
39
39
|
exports.isExtensionSupported = isExtensionSupported;
|
|
@@ -50,12 +50,12 @@ __exportStar(require("./processors"), exports);
|
|
|
50
50
|
// ===================================================================
|
|
51
51
|
// NAMESPACES
|
|
52
52
|
// ===================================================================
|
|
53
|
-
// Analytics namespace
|
|
54
|
-
exports.Analytics = __importStar(require("./
|
|
55
|
-
// Also export analytics classes directly for convenience
|
|
56
|
-
__exportStar(require("./utilities/analytics"), exports);
|
|
53
|
+
// Analytics namespace (usage/history)
|
|
54
|
+
exports.Analytics = __importStar(require("./analytics"));
|
|
57
55
|
// Validation namespace
|
|
58
56
|
exports.Validation = __importStar(require("./validation"));
|
|
57
|
+
// Metrics namespace (pageset analytics)
|
|
58
|
+
exports.Metrics = __importStar(require("./metrics"));
|
|
59
59
|
// Processor namespaces (platform-specific utilities)
|
|
60
60
|
exports.Gridset = __importStar(require("./gridset"));
|
|
61
61
|
exports.Snap = __importStar(require("./snap"));
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metrics Namespace
|
|
3
|
+
*
|
|
4
|
+
* Pageset-focused metrics and analytics (board structure, effort, vocabulary).
|
|
5
|
+
* Use this for analyzing AAC trees, not end-user usage logs.
|
|
6
|
+
*/
|
|
7
|
+
export * from './utilities/analytics/metrics/types';
|
|
8
|
+
export * from './utilities/analytics/metrics/effort';
|
|
9
|
+
export * from './utilities/analytics/metrics/obl-types';
|
|
10
|
+
export { OblUtil, OblAnonymizer } from './utilities/analytics/metrics/obl';
|
|
11
|
+
export { MetricsCalculator } from './utilities/analytics/metrics/core';
|
|
12
|
+
export { VocabularyAnalyzer } from './utilities/analytics/metrics/vocabulary';
|
|
13
|
+
export { SentenceAnalyzer } from './utilities/analytics/metrics/sentence';
|
|
14
|
+
export { ComparisonAnalyzer } from './utilities/analytics/metrics/comparison';
|
|
15
|
+
export { ReferenceLoader } from './utilities/analytics/reference';
|
|
16
|
+
export { InMemoryReferenceLoader, createBrowserReferenceLoader, loadReferenceDataFromUrl, type ReferenceData, } from './utilities/analytics/reference/browser';
|
|
17
|
+
export * from './utilities/analytics/utils/idGenerator';
|
package/dist/metrics.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Metrics Namespace
|
|
4
|
+
*
|
|
5
|
+
* Pageset-focused metrics and analytics (board structure, effort, vocabulary).
|
|
6
|
+
* Use this for analyzing AAC trees, not end-user usage logs.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
20
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
21
|
+
};
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.loadReferenceDataFromUrl = exports.createBrowserReferenceLoader = exports.InMemoryReferenceLoader = exports.ReferenceLoader = exports.ComparisonAnalyzer = exports.SentenceAnalyzer = exports.VocabularyAnalyzer = exports.MetricsCalculator = exports.OblAnonymizer = exports.OblUtil = void 0;
|
|
24
|
+
__exportStar(require("./utilities/analytics/metrics/types"), exports);
|
|
25
|
+
__exportStar(require("./utilities/analytics/metrics/effort"), exports);
|
|
26
|
+
__exportStar(require("./utilities/analytics/metrics/obl-types"), exports);
|
|
27
|
+
var obl_1 = require("./utilities/analytics/metrics/obl");
|
|
28
|
+
Object.defineProperty(exports, "OblUtil", { enumerable: true, get: function () { return obl_1.OblUtil; } });
|
|
29
|
+
Object.defineProperty(exports, "OblAnonymizer", { enumerable: true, get: function () { return obl_1.OblAnonymizer; } });
|
|
30
|
+
var core_1 = require("./utilities/analytics/metrics/core");
|
|
31
|
+
Object.defineProperty(exports, "MetricsCalculator", { enumerable: true, get: function () { return core_1.MetricsCalculator; } });
|
|
32
|
+
var vocabulary_1 = require("./utilities/analytics/metrics/vocabulary");
|
|
33
|
+
Object.defineProperty(exports, "VocabularyAnalyzer", { enumerable: true, get: function () { return vocabulary_1.VocabularyAnalyzer; } });
|
|
34
|
+
var sentence_1 = require("./utilities/analytics/metrics/sentence");
|
|
35
|
+
Object.defineProperty(exports, "SentenceAnalyzer", { enumerable: true, get: function () { return sentence_1.SentenceAnalyzer; } });
|
|
36
|
+
var comparison_1 = require("./utilities/analytics/metrics/comparison");
|
|
37
|
+
Object.defineProperty(exports, "ComparisonAnalyzer", { enumerable: true, get: function () { return comparison_1.ComparisonAnalyzer; } });
|
|
38
|
+
var reference_1 = require("./utilities/analytics/reference");
|
|
39
|
+
Object.defineProperty(exports, "ReferenceLoader", { enumerable: true, get: function () { return reference_1.ReferenceLoader; } });
|
|
40
|
+
var browser_1 = require("./utilities/analytics/reference/browser");
|
|
41
|
+
Object.defineProperty(exports, "InMemoryReferenceLoader", { enumerable: true, get: function () { return browser_1.InMemoryReferenceLoader; } });
|
|
42
|
+
Object.defineProperty(exports, "createBrowserReferenceLoader", { enumerable: true, get: function () { return browser_1.createBrowserReferenceLoader; } });
|
|
43
|
+
Object.defineProperty(exports, "loadReferenceDataFromUrl", { enumerable: true, get: function () { return browser_1.loadReferenceDataFromUrl; } });
|
|
44
|
+
__exportStar(require("./utilities/analytics/utils/idGenerator"), exports);
|
|
@@ -651,6 +651,27 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
651
651
|
});
|
|
652
652
|
});
|
|
653
653
|
}
|
|
654
|
+
const pageWordListItems = [];
|
|
655
|
+
if (grid.WordList && grid.WordList.Items) {
|
|
656
|
+
const items = grid.WordList.Items.WordListItem || grid.WordList.Items.wordlistitem || [];
|
|
657
|
+
const itemArr = Array.isArray(items) ? items : items ? [items] : [];
|
|
658
|
+
for (const item of itemArr) {
|
|
659
|
+
const text = item.Text || item.text;
|
|
660
|
+
if (text) {
|
|
661
|
+
const val = this.textOf(text);
|
|
662
|
+
if (val) {
|
|
663
|
+
pageWordListItems.push({
|
|
664
|
+
text: val,
|
|
665
|
+
image: item.Image || item.image || undefined,
|
|
666
|
+
partOfSpeech: item.PartOfSpeech || item.partOfSpeech || undefined,
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
// Track WordList AutoContent cells and their positions for "more" button placement
|
|
673
|
+
const wordListAutoContentCells = [];
|
|
674
|
+
let wordListCellIndex = 0;
|
|
654
675
|
cellArr.forEach((cell, idx) => {
|
|
655
676
|
if (!cell || !cell.Content)
|
|
656
677
|
return;
|
|
@@ -718,7 +739,7 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
718
739
|
return; // Skip cells without labels AND without images/symbols
|
|
719
740
|
}
|
|
720
741
|
}
|
|
721
|
-
|
|
742
|
+
let message = label; // Use caption as message
|
|
722
743
|
// Detect plugin cell type (Workspace, LiveCell, AutoContent)
|
|
723
744
|
const pluginMetadata = (0, pluginTypes_1.detectPluginCellType)(content);
|
|
724
745
|
// Friendly labels for workspace/prediction cells when captions are missing
|
|
@@ -737,6 +758,45 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
737
758
|
// Always surface a friendly label for predictions even if a placeholder exists
|
|
738
759
|
label = `Prediction ${predictionCellCounter}`;
|
|
739
760
|
}
|
|
761
|
+
// Handle WordList AutoContent cells - populate from page-level WordList
|
|
762
|
+
let isMoreButton = false;
|
|
763
|
+
if (pluginMetadata.cellType === pluginTypes_1.Grid3CellType.AutoContent &&
|
|
764
|
+
pluginMetadata.autoContentType === 'WordList' &&
|
|
765
|
+
pageWordListItems.length > 0) {
|
|
766
|
+
// Track this cell for potential "more" button
|
|
767
|
+
wordListAutoContentCells.push({
|
|
768
|
+
cell,
|
|
769
|
+
idx,
|
|
770
|
+
x: cellX,
|
|
771
|
+
y: cellY,
|
|
772
|
+
});
|
|
773
|
+
// Check if we have more WordList items than available cells
|
|
774
|
+
// The "more" button replaces the last WordList cell
|
|
775
|
+
const cellsNeededForWordList = pageWordListItems.length;
|
|
776
|
+
const availableWordListCells = wordListAutoContentCells.length;
|
|
777
|
+
const isLastWordListCell = availableWordListCells === cellsNeededForWordList + 1; // +1 for "more" button
|
|
778
|
+
if (isLastWordListCell) {
|
|
779
|
+
// This cell becomes the "more" button
|
|
780
|
+
label = 'more...';
|
|
781
|
+
message = 'more...';
|
|
782
|
+
isMoreButton = true;
|
|
783
|
+
}
|
|
784
|
+
else if (wordListCellIndex < pageWordListItems.length) {
|
|
785
|
+
// Populate this cell with the next WordList item
|
|
786
|
+
const wordListItem = pageWordListItems[wordListCellIndex];
|
|
787
|
+
label = wordListItem.text;
|
|
788
|
+
message = wordListItem.text;
|
|
789
|
+
// Use the WordList item's image if available
|
|
790
|
+
if (wordListItem.image && !label) {
|
|
791
|
+
label = wordListItem.image; // Fallback to image path if no text
|
|
792
|
+
}
|
|
793
|
+
wordListCellIndex++;
|
|
794
|
+
}
|
|
795
|
+
else {
|
|
796
|
+
// No more WordList items - skip this cell
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
740
800
|
// Parse all command types from Grid3 and create semantic actions
|
|
741
801
|
let semanticAction;
|
|
742
802
|
let legacyAction = null;
|
|
@@ -1311,6 +1371,13 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
1311
1371
|
: undefined,
|
|
1312
1372
|
// Store page name for Grid3 image lookup
|
|
1313
1373
|
gridPageName: gridName,
|
|
1374
|
+
// Store WordList "more" button flag
|
|
1375
|
+
isMoreButton: isMoreButton || undefined,
|
|
1376
|
+
wordListItemIndex: pluginMetadata.cellType === pluginTypes_1.Grid3CellType.AutoContent &&
|
|
1377
|
+
pluginMetadata.autoContentType === 'WordList' &&
|
|
1378
|
+
!isMoreButton
|
|
1379
|
+
? wordListCellIndex - 1
|
|
1380
|
+
: undefined,
|
|
1314
1381
|
},
|
|
1315
1382
|
});
|
|
1316
1383
|
// Add button to page
|
|
@@ -517,19 +517,27 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
517
517
|
columns,
|
|
518
518
|
order,
|
|
519
519
|
},
|
|
520
|
-
buttons: page.buttons.map((button) =>
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
:
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
520
|
+
buttons: page.buttons.map((button) => {
|
|
521
|
+
const extraButtonInfo = button;
|
|
522
|
+
const imageId = button.parameters?.image_id ||
|
|
523
|
+
button.parameters?.imageId ||
|
|
524
|
+
extraButtonInfo.image_id ||
|
|
525
|
+
extraButtonInfo.imageId;
|
|
526
|
+
return {
|
|
527
|
+
id: button.id,
|
|
528
|
+
label: button.label,
|
|
529
|
+
vocalization: button.message || button.label,
|
|
530
|
+
load_board: button.semanticAction?.intent === treeStructure_1.AACSemanticIntent.NAVIGATE_TO && button.targetPageId
|
|
531
|
+
? {
|
|
532
|
+
path: button.targetPageId,
|
|
533
|
+
}
|
|
534
|
+
: undefined,
|
|
535
|
+
background_color: button.style?.backgroundColor,
|
|
536
|
+
border_color: button.style?.borderColor,
|
|
537
|
+
box_id: buttonPositions.get(String(button.id ?? '')),
|
|
538
|
+
image_id: imageId,
|
|
539
|
+
};
|
|
540
|
+
}),
|
|
533
541
|
images: Array.isArray(page.images) ? page.images : [],
|
|
534
542
|
sounds: Array.isArray(page.sounds) ? page.sounds : [],
|
|
535
543
|
};
|