@willwade/aac-processors 0.2.16 → 0.2.18
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/browser/processors/gridset/commands.js +56 -1
- package/dist/browser/processors/gridsetProcessor.js +16 -2
- package/dist/browser/processors/snapProcessor.js +42 -4
- package/dist/browser/utilities/analytics/metrics/core.js +182 -33
- package/dist/browser/utilities/analytics/metrics/effort.js +1 -0
- package/dist/browser/utilities/analytics/morphology/engine.js +24 -2
- package/dist/browser/utilities/analytics/morphology/index.js +1 -0
- package/dist/browser/utilities/analytics/morphology/tdsnapLexiconParser.js +182 -0
- package/dist/core/treeStructure.d.ts +3 -2
- package/dist/index.node.d.ts +1 -0
- package/dist/index.node.js +4 -2
- package/dist/processors/gridset/commands.js +56 -1
- package/dist/processors/gridsetProcessor.js +16 -2
- package/dist/processors/snapProcessor.js +42 -4
- package/dist/types/aac.d.ts +1 -1
- package/dist/utilities/analytics/metrics/core.d.ts +33 -0
- package/dist/utilities/analytics/metrics/core.js +182 -33
- package/dist/utilities/analytics/metrics/effort.d.ts +1 -0
- package/dist/utilities/analytics/metrics/effort.js +1 -0
- package/dist/utilities/analytics/metrics/types.d.ts +26 -0
- package/dist/utilities/analytics/morphology/engine.d.ts +4 -0
- package/dist/utilities/analytics/morphology/engine.js +24 -2
- package/dist/utilities/analytics/morphology/index.d.ts +2 -0
- package/dist/utilities/analytics/morphology/index.js +3 -1
- package/dist/utilities/analytics/morphology/tdsnapLexiconParser.d.ts +28 -0
- package/dist/utilities/analytics/morphology/tdsnapLexiconParser.js +186 -0
- package/package.json +5 -5
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
export class TDSnapLexiconParser {
|
|
2
|
+
parseDb(dbPath, locale) {
|
|
3
|
+
const detectedLocale = locale || this.inferLocale(dbPath);
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
5
|
+
const Database = require('better-sqlite3');
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
7
|
+
const db = new Database(dbPath, { readonly: true });
|
|
8
|
+
try {
|
|
9
|
+
return this.extractAll(db, detectedLocale);
|
|
10
|
+
}
|
|
11
|
+
finally {
|
|
12
|
+
db.close();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
inferLocale(dbPath) {
|
|
16
|
+
const match = dbPath.match(/lang_([a-z]{2}_[A-Z]{2})/i);
|
|
17
|
+
return match ? match[1] : 'unknown';
|
|
18
|
+
}
|
|
19
|
+
extractAll(db, locale) {
|
|
20
|
+
const words = new Map();
|
|
21
|
+
const subclassCache = new Map();
|
|
22
|
+
const getSubclass = (id) => {
|
|
23
|
+
let name = subclassCache.get(id);
|
|
24
|
+
if (name !== undefined)
|
|
25
|
+
return name;
|
|
26
|
+
const row = db.prepare('SELECT Name FROM PosSubclass WHERE Id = ?').get(id);
|
|
27
|
+
name = row?.Name;
|
|
28
|
+
if (name) {
|
|
29
|
+
subclassCache.set(id, name);
|
|
30
|
+
return name;
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
};
|
|
34
|
+
const allWords = db
|
|
35
|
+
.prepare(`SELECT w.Id as wordId, w.Text as text,
|
|
36
|
+
i.Id as inflectionId, i.LexemeId as lexemeId, i.PosSubclassId as posSubclassId
|
|
37
|
+
FROM Word w
|
|
38
|
+
JOIN Spelling s ON s.WordId = w.Id
|
|
39
|
+
JOIN Inflection i ON i.Id = s.InflectionId
|
|
40
|
+
WHERE i.PosSubclassId != 0
|
|
41
|
+
ORDER BY w.Text`)
|
|
42
|
+
.all();
|
|
43
|
+
const lexemeForms = new Map();
|
|
44
|
+
for (const row of allWords) {
|
|
45
|
+
const tag = getSubclass(row.posSubclassId);
|
|
46
|
+
if (!tag)
|
|
47
|
+
continue;
|
|
48
|
+
let formsByTag = lexemeForms.get(row.lexemeId);
|
|
49
|
+
if (!formsByTag) {
|
|
50
|
+
formsByTag = new Map();
|
|
51
|
+
lexemeForms.set(row.lexemeId, formsByTag);
|
|
52
|
+
}
|
|
53
|
+
const existing = formsByTag.get(tag);
|
|
54
|
+
if (existing) {
|
|
55
|
+
if (!existing.includes(row.text))
|
|
56
|
+
existing.push(row.text);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
formsByTag.set(tag, [row.text]);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const wordToLexeme = new Map();
|
|
63
|
+
for (const row of allWords) {
|
|
64
|
+
if (!wordToLexeme.has(row.text.toLowerCase())) {
|
|
65
|
+
wordToLexeme.set(row.text.toLowerCase(), row.lexemeId);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
for (const [text, lexemeId] of wordToLexeme) {
|
|
69
|
+
const formsByTag = lexemeForms.get(lexemeId);
|
|
70
|
+
if (!formsByTag || formsByTag.size === 0)
|
|
71
|
+
continue;
|
|
72
|
+
const forms = [];
|
|
73
|
+
for (const [tag, formTexts] of formsByTag) {
|
|
74
|
+
for (const formText of formTexts) {
|
|
75
|
+
if (formText.toLowerCase() !== text) {
|
|
76
|
+
forms.push({ tag, form: formText });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (forms.length > 0) {
|
|
81
|
+
words.set(text, { lexemeId, forms });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return { locale, words };
|
|
85
|
+
}
|
|
86
|
+
lookupWord(data, word) {
|
|
87
|
+
const entry = data.words.get(word.toLowerCase());
|
|
88
|
+
if (!entry)
|
|
89
|
+
return [];
|
|
90
|
+
return entry.forms.map((f) => f.form);
|
|
91
|
+
}
|
|
92
|
+
lookupWordByTag(data, word, tag) {
|
|
93
|
+
const entry = data.words.get(word.toLowerCase());
|
|
94
|
+
if (!entry)
|
|
95
|
+
return [];
|
|
96
|
+
return entry.forms.filter((f) => f.tag === tag).map((f) => f.form);
|
|
97
|
+
}
|
|
98
|
+
static parseContentTypeHandler(handler) {
|
|
99
|
+
if (!handler)
|
|
100
|
+
return null;
|
|
101
|
+
const colonIdx = handler.indexOf(':');
|
|
102
|
+
if (colonIdx === -1) {
|
|
103
|
+
const parts = handler.split(',');
|
|
104
|
+
return { category: parts[0], subtype: '', params: parts.slice(1) };
|
|
105
|
+
}
|
|
106
|
+
const category = handler.substring(0, colonIdx);
|
|
107
|
+
const rest = handler.substring(colonIdx + 1);
|
|
108
|
+
const commaIdx = rest.indexOf(',');
|
|
109
|
+
if (commaIdx === -1) {
|
|
110
|
+
return { category, subtype: rest, params: [] };
|
|
111
|
+
}
|
|
112
|
+
const subtype = rest.substring(0, commaIdx);
|
|
113
|
+
const paramsStr = rest.substring(commaIdx + 1);
|
|
114
|
+
const params = paramsStr.split(',').map((p) => p.trim());
|
|
115
|
+
return { category, subtype, params };
|
|
116
|
+
}
|
|
117
|
+
static tagToPos(tag) {
|
|
118
|
+
return TDSnapLexiconParser.TAG_TO_POS[tag] || 'Unknown';
|
|
119
|
+
}
|
|
120
|
+
static handlerToPos(handler) {
|
|
121
|
+
const parsed = TDSnapLexiconParser.parseContentTypeHandler(handler);
|
|
122
|
+
if (!parsed)
|
|
123
|
+
return 'Unknown';
|
|
124
|
+
if (parsed.category === 'RESET' || parsed.category === 'SPECIAL')
|
|
125
|
+
return 'Ignore';
|
|
126
|
+
const key = `${parsed.category}:${parsed.subtype}`;
|
|
127
|
+
const tag = TDSnapLexiconParser.HANDLER_TAG_MAP[key];
|
|
128
|
+
if (tag)
|
|
129
|
+
return TDSnapLexiconParser.TAG_TO_POS[tag] || 'Unknown';
|
|
130
|
+
return TDSnapLexiconParser.TAG_TO_POS[parsed.subtype] || 'Unknown';
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
TDSnapLexiconParser.TAG_TO_POS = {
|
|
134
|
+
V0: 'Verb',
|
|
135
|
+
VZ: 'Verb',
|
|
136
|
+
VG: 'Verb',
|
|
137
|
+
VD: 'Verb',
|
|
138
|
+
VN: 'Verb',
|
|
139
|
+
SNG: 'Noun',
|
|
140
|
+
PLU: 'Noun',
|
|
141
|
+
ADJ: 'Adjective',
|
|
142
|
+
ADJR: 'Adjective',
|
|
143
|
+
ADJT: 'Adjective',
|
|
144
|
+
ADV: 'Adjective',
|
|
145
|
+
SUB: 'Pronoun',
|
|
146
|
+
OBJ: 'Pronoun',
|
|
147
|
+
POS: 'Pronoun',
|
|
148
|
+
NPOS: 'Pronoun',
|
|
149
|
+
REF: 'Pronoun',
|
|
150
|
+
B0: 'Verb',
|
|
151
|
+
BZ: 'Verb',
|
|
152
|
+
BM: 'Verb',
|
|
153
|
+
BR: 'Verb',
|
|
154
|
+
BDZ: 'Verb',
|
|
155
|
+
BDR: 'Verb',
|
|
156
|
+
BG: 'Verb',
|
|
157
|
+
BN: 'Verb',
|
|
158
|
+
};
|
|
159
|
+
TDSnapLexiconParser.HANDLER_TAG_MAP = {
|
|
160
|
+
'NOUN:PLU': 'PLU',
|
|
161
|
+
'DESCRIBE:ADJR': 'ADJR',
|
|
162
|
+
'DESCRIBE:ADJT': 'ADJT',
|
|
163
|
+
'DESCRIBE:ADV': 'ADV',
|
|
164
|
+
'VERB:V0': 'V0',
|
|
165
|
+
'VERB:VZ': 'VZ',
|
|
166
|
+
'VERB:VG': 'VG',
|
|
167
|
+
'VERB:VD': 'VD',
|
|
168
|
+
'VERB:VN': 'VN',
|
|
169
|
+
'PRONOUN:SUB': 'SUB',
|
|
170
|
+
'PRONOUN:OBJ': 'OBJ',
|
|
171
|
+
'PRONOUN:POS': 'POS',
|
|
172
|
+
'PRONOUN:NPOS': 'NPOS',
|
|
173
|
+
'PRONOUN:REF': 'REF',
|
|
174
|
+
'BE:B0': 'B0',
|
|
175
|
+
'BE:BZ': 'BZ',
|
|
176
|
+
'BE:BM': 'BM',
|
|
177
|
+
'BE:BR': 'BR',
|
|
178
|
+
'BE:BDZ': 'BDZ',
|
|
179
|
+
'BE:BDR': 'BDR',
|
|
180
|
+
'BE:BG': 'BG',
|
|
181
|
+
'BE:BN': 'BN',
|
|
182
|
+
};
|
|
@@ -121,7 +121,7 @@ export declare class AACButton {
|
|
|
121
121
|
identifier?: string;
|
|
122
122
|
metadata?: string;
|
|
123
123
|
};
|
|
124
|
-
contentType?: 'Normal' | 'AutoContent' | 'Workspace' | 'LiveCell';
|
|
124
|
+
contentType?: 'Normal' | 'AutoContent' | 'Workspace' | 'LiveCell' | 'Inflector' | 'Prediction';
|
|
125
125
|
contentSubType?: string;
|
|
126
126
|
image?: string;
|
|
127
127
|
resolvedImageEntry?: string;
|
|
@@ -169,7 +169,7 @@ export declare class AACButton {
|
|
|
169
169
|
metadata?: string;
|
|
170
170
|
};
|
|
171
171
|
style?: AACStyle;
|
|
172
|
-
contentType?: 'Normal' | 'AutoContent' | 'Workspace' | 'LiveCell';
|
|
172
|
+
contentType?: 'Normal' | 'AutoContent' | 'Workspace' | 'LiveCell' | 'Inflector' | 'Prediction';
|
|
173
173
|
contentSubType?: string;
|
|
174
174
|
image?: string;
|
|
175
175
|
resolvedImageEntry?: string;
|
|
@@ -222,6 +222,7 @@ export declare class AACPage {
|
|
|
222
222
|
descriptionHtml?: string;
|
|
223
223
|
images?: any[];
|
|
224
224
|
sounds?: any[];
|
|
225
|
+
wordListItems?: import('../types/aac').AACWordListItem[];
|
|
225
226
|
semantic_ids?: string[];
|
|
226
227
|
clone_ids?: string[];
|
|
227
228
|
scanningConfig?: import('../types/aac').ScanningConfig;
|
package/dist/index.node.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export * as Analytics from './analytics';
|
|
|
13
13
|
export * as Validation from './validation';
|
|
14
14
|
export * as Metrics from './metrics';
|
|
15
15
|
export { Grid3VerbsParser } from './utilities/analytics/morphology/grid3VerbsParser';
|
|
16
|
+
export { TDSnapLexiconParser } from './utilities/analytics/morphology/tdsnapLexiconParser';
|
|
16
17
|
export { WordFormGenerator } from './utilities/analytics/morphology/wordFormGenerator';
|
|
17
18
|
export * as Gridset from './gridset';
|
|
18
19
|
export * as Snap from './snap';
|
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.WordFormGenerator = exports.Grid3VerbsParser = exports.Metrics = 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.WordFormGenerator = exports.TDSnapLexiconParser = exports.Grid3VerbsParser = exports.Metrics = exports.Validation = exports.Analytics = void 0;
|
|
37
37
|
exports.getProcessor = getProcessor;
|
|
38
38
|
exports.getSupportedExtensions = getSupportedExtensions;
|
|
39
39
|
exports.isExtensionSupported = isExtensionSupported;
|
|
@@ -56,9 +56,11 @@ exports.Analytics = __importStar(require("./analytics"));
|
|
|
56
56
|
exports.Validation = __importStar(require("./validation"));
|
|
57
57
|
// Metrics namespace (pageset analytics)
|
|
58
58
|
exports.Metrics = __importStar(require("./metrics"));
|
|
59
|
-
// Node-only morphology utilities (Grid 3 verbs parser)
|
|
59
|
+
// Node-only morphology utilities (Grid 3 verbs parser, TDSnap lexicon parser)
|
|
60
60
|
var grid3VerbsParser_1 = require("./utilities/analytics/morphology/grid3VerbsParser");
|
|
61
61
|
Object.defineProperty(exports, "Grid3VerbsParser", { enumerable: true, get: function () { return grid3VerbsParser_1.Grid3VerbsParser; } });
|
|
62
|
+
var tdsnapLexiconParser_1 = require("./utilities/analytics/morphology/tdsnapLexiconParser");
|
|
63
|
+
Object.defineProperty(exports, "TDSnapLexiconParser", { enumerable: true, get: function () { return tdsnapLexiconParser_1.TDSnapLexiconParser; } });
|
|
62
64
|
var wordFormGenerator_1 = require("./utilities/analytics/morphology/wordFormGenerator");
|
|
63
65
|
Object.defineProperty(exports, "WordFormGenerator", { enumerable: true, get: function () { return wordFormGenerator_1.WordFormGenerator; } });
|
|
64
66
|
// Processor namespaces (platform-specific utilities)
|
|
@@ -920,6 +920,61 @@ function getAllPluginIds() {
|
|
|
920
920
|
const plugins = new Set(Object.values(exports.GRID3_COMMANDS).map((cmd) => cmd.pluginId));
|
|
921
921
|
return Array.from(plugins).sort();
|
|
922
922
|
}
|
|
923
|
+
function textOfStructured(val) {
|
|
924
|
+
if (!val || typeof val !== 'object')
|
|
925
|
+
return undefined;
|
|
926
|
+
const parts = [];
|
|
927
|
+
const processS = (s) => {
|
|
928
|
+
if (!s)
|
|
929
|
+
return;
|
|
930
|
+
if (s.r !== undefined) {
|
|
931
|
+
const rElements = Array.isArray(s.r) ? s.r : [s.r];
|
|
932
|
+
for (const r of rElements) {
|
|
933
|
+
if (typeof r === 'number') {
|
|
934
|
+
if (r !== 0)
|
|
935
|
+
parts.push(String(r));
|
|
936
|
+
continue;
|
|
937
|
+
}
|
|
938
|
+
if (typeof r === 'object' && r !== null) {
|
|
939
|
+
if ('#text' in r)
|
|
940
|
+
parts.push(String(r['#text']));
|
|
941
|
+
else if ('#cdata' in r)
|
|
942
|
+
parts.push(String(r['#cdata']));
|
|
943
|
+
else
|
|
944
|
+
parts.push(String(r));
|
|
945
|
+
}
|
|
946
|
+
else {
|
|
947
|
+
parts.push(String(r));
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
};
|
|
952
|
+
if (val.p) {
|
|
953
|
+
const sElements = Array.isArray(val.p.s) ? val.p.s : val.p.s ? [val.p.s] : [];
|
|
954
|
+
sElements.forEach(processS);
|
|
955
|
+
}
|
|
956
|
+
else if (val.s) {
|
|
957
|
+
const sElements = Array.isArray(val.s) ? val.s : [val.s];
|
|
958
|
+
sElements.forEach(processS);
|
|
959
|
+
}
|
|
960
|
+
else if (val.r !== undefined) {
|
|
961
|
+
processS(val);
|
|
962
|
+
}
|
|
963
|
+
return parts.length > 0 ? parts.join('').trim() : undefined;
|
|
964
|
+
}
|
|
965
|
+
function extractParamValue(param) {
|
|
966
|
+
if (typeof param === 'string')
|
|
967
|
+
return param;
|
|
968
|
+
if (param.p || param.s || (param.r !== undefined && typeof param.r !== 'string')) {
|
|
969
|
+
const structured = textOfStructured(param);
|
|
970
|
+
if (structured !== undefined)
|
|
971
|
+
return structured;
|
|
972
|
+
}
|
|
973
|
+
const simple = param['#text'] ?? param.text ?? param.value;
|
|
974
|
+
if (simple !== undefined)
|
|
975
|
+
return simple;
|
|
976
|
+
return textOfStructured(param);
|
|
977
|
+
}
|
|
923
978
|
function extractCommandParameters(command) {
|
|
924
979
|
const parameters = {};
|
|
925
980
|
const params = command.Parameter || command.parameter;
|
|
@@ -928,7 +983,7 @@ function extractCommandParameters(command) {
|
|
|
928
983
|
const paramArray = Array.isArray(params) ? params : [params];
|
|
929
984
|
for (const param of paramArray) {
|
|
930
985
|
const key = param['@_Key'] || param.Key || param.key;
|
|
931
|
-
let value = param
|
|
986
|
+
let value = extractParamValue(param);
|
|
932
987
|
if (key && value !== undefined) {
|
|
933
988
|
// Try to convert to number if it looks numeric
|
|
934
989
|
if (typeof value === 'string' && /^\d+$/.test(value)) {
|
|
@@ -703,6 +703,13 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
703
703
|
}
|
|
704
704
|
}
|
|
705
705
|
}
|
|
706
|
+
if (pageWordListItems.length > 0) {
|
|
707
|
+
page.wordListItems = pageWordListItems.map((item) => ({
|
|
708
|
+
text: item.text,
|
|
709
|
+
image: item.image,
|
|
710
|
+
partOfSpeech: item.partOfSpeech,
|
|
711
|
+
}));
|
|
712
|
+
}
|
|
706
713
|
// Track WordList AutoContent cells and their positions for "more" button placement
|
|
707
714
|
const wordListAutoContentCells = [];
|
|
708
715
|
let wordListCellIndex = 0;
|
|
@@ -1035,6 +1042,15 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
1035
1042
|
const param = getRawParam(key);
|
|
1036
1043
|
if (param === undefined)
|
|
1037
1044
|
return undefined;
|
|
1045
|
+
if (typeof param === 'string')
|
|
1046
|
+
return param;
|
|
1047
|
+
if (param.p ||
|
|
1048
|
+
param.s ||
|
|
1049
|
+
(param.r !== undefined && typeof param.r !== 'string')) {
|
|
1050
|
+
const structuredValue = this.textOf(param);
|
|
1051
|
+
if (structuredValue !== undefined)
|
|
1052
|
+
return structuredValue;
|
|
1053
|
+
}
|
|
1038
1054
|
const simpleValue = param['#text'] ?? param.text ?? param.value;
|
|
1039
1055
|
if (typeof simpleValue === 'string')
|
|
1040
1056
|
return simpleValue;
|
|
@@ -1043,8 +1059,6 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
1043
1059
|
const structuredValue = this.textOf(param);
|
|
1044
1060
|
if (structuredValue !== undefined)
|
|
1045
1061
|
return structuredValue;
|
|
1046
|
-
if (typeof param === 'string')
|
|
1047
|
-
return param;
|
|
1048
1062
|
return undefined;
|
|
1049
1063
|
};
|
|
1050
1064
|
// Skip PredictThis in primary action loop as it was handled in pre-pass
|
|
@@ -336,6 +336,9 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
336
336
|
buttonColumns.has('BackgroundColor') ? 'b.BackgroundColor' : 'NULL AS BackgroundColor',
|
|
337
337
|
buttonColumns.has('NavigatePageId') ? 'b.NavigatePageId' : 'NULL AS NavigatePageId',
|
|
338
338
|
buttonColumns.has('ContentType') ? 'b.ContentType' : 'NULL AS ContentType',
|
|
339
|
+
buttonColumns.has('SerializedContentTypeHandler')
|
|
340
|
+
? 'b.SerializedContentTypeHandler'
|
|
341
|
+
: 'NULL AS SerializedContentTypeHandler',
|
|
339
342
|
];
|
|
340
343
|
if (this.loadAudio) {
|
|
341
344
|
selectFields.push(buttonColumns.has('MessageRecordingId')
|
|
@@ -551,22 +554,54 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
551
554
|
},
|
|
552
555
|
};
|
|
553
556
|
}
|
|
557
|
+
let snapContentType;
|
|
558
|
+
let snapContentSubType;
|
|
559
|
+
let snapGrammarPos;
|
|
560
|
+
let snapGrammarHandler;
|
|
561
|
+
if (btnRow.ContentType === 1) {
|
|
562
|
+
snapContentType = 'AutoContent';
|
|
563
|
+
snapContentSubType = 'Prediction';
|
|
564
|
+
}
|
|
565
|
+
else if (btnRow.ContentType === 3 && btnRow.SerializedContentTypeHandler) {
|
|
566
|
+
snapContentType = 'Inflector';
|
|
567
|
+
snapGrammarHandler = String(btnRow.SerializedContentTypeHandler);
|
|
568
|
+
const colonIdx = snapGrammarHandler.indexOf(':');
|
|
569
|
+
if (colonIdx !== -1) {
|
|
570
|
+
const subtype = snapGrammarHandler.substring(colonIdx + 1).split(',')[0];
|
|
571
|
+
snapContentSubType = subtype;
|
|
572
|
+
const { TDSnapLexiconParser, } = require('../utilities/analytics/morphology/tdsnapLexiconParser');
|
|
573
|
+
snapGrammarPos = TDSnapLexiconParser.tagToPos(subtype);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
554
576
|
const button = new treeStructure_1.AACButton({
|
|
555
577
|
id: String(btnRow.Id),
|
|
556
578
|
label: btnRow.Label || (btnRow.ContentType === 1 ? '[Prediction]' : ''),
|
|
557
579
|
message: btnRow.Message || (btnRow.ContentType === 1 ? '[Prediction]' : btnRow.Label || ''),
|
|
558
580
|
targetPageId: targetPageUniqueId,
|
|
559
581
|
semanticAction: semanticAction,
|
|
560
|
-
contentType:
|
|
561
|
-
contentSubType:
|
|
582
|
+
contentType: snapContentType,
|
|
583
|
+
contentSubType: snapContentSubType,
|
|
562
584
|
audioRecording: audioRecording,
|
|
563
585
|
visibility: mapSnapVisibility(btnRow.Visible),
|
|
564
586
|
semantic_id: btnRow.LibrarySymbolId
|
|
565
587
|
? `snap_symbol_${btnRow.LibrarySymbolId}`
|
|
566
|
-
: undefined,
|
|
588
|
+
: undefined,
|
|
567
589
|
image: buttonImage,
|
|
568
590
|
resolvedImageEntry: buttonImage,
|
|
569
|
-
parameters:
|
|
591
|
+
parameters: {
|
|
592
|
+
...(Object.keys(buttonParameters).length > 0 ? buttonParameters : {}),
|
|
593
|
+
...(snapGrammarHandler
|
|
594
|
+
? {
|
|
595
|
+
grammar: {
|
|
596
|
+
handler: snapGrammarHandler,
|
|
597
|
+
category: snapGrammarHandler.substring(0, snapGrammarHandler.indexOf(':') !== -1
|
|
598
|
+
? snapGrammarHandler.indexOf(':')
|
|
599
|
+
: snapGrammarHandler.length),
|
|
600
|
+
subtype: snapContentSubType,
|
|
601
|
+
},
|
|
602
|
+
}
|
|
603
|
+
: {}),
|
|
604
|
+
},
|
|
570
605
|
style: {
|
|
571
606
|
backgroundColor: btnRow.BackgroundColor
|
|
572
607
|
? `#${btnRow.BackgroundColor.toString(16)}`
|
|
@@ -579,6 +614,9 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
579
614
|
fontStyle: btnRow.FontStyle?.toString(),
|
|
580
615
|
},
|
|
581
616
|
});
|
|
617
|
+
if (snapGrammarPos) {
|
|
618
|
+
button.pos = snapGrammarPos;
|
|
619
|
+
}
|
|
582
620
|
// Add to the intended parent page
|
|
583
621
|
const parentPage = tree.getPage(parentUniqueId);
|
|
584
622
|
if (parentPage) {
|
package/dist/types/aac.d.ts
CHANGED
|
@@ -83,7 +83,7 @@ export interface AACButton {
|
|
|
83
83
|
identifier?: string;
|
|
84
84
|
metadata?: string;
|
|
85
85
|
};
|
|
86
|
-
contentType?: 'Normal' | 'AutoContent' | 'Workspace' | 'LiveCell';
|
|
86
|
+
contentType?: 'Normal' | 'AutoContent' | 'Workspace' | 'LiveCell' | 'Inflector' | 'Prediction';
|
|
87
87
|
contentSubType?: string;
|
|
88
88
|
image?: string;
|
|
89
89
|
resolvedImageEntry?: string;
|
|
@@ -42,6 +42,10 @@ export declare class MetricsCalculator {
|
|
|
42
42
|
/**
|
|
43
43
|
* Quick check whether any button in the tree has a POS tag.
|
|
44
44
|
* Used to auto-enable smart grammar without requiring explicit opt-in.
|
|
45
|
+
*
|
|
46
|
+
* IMPORTANT: Only counts POS from non-Inflector and non-Suffix buttons.
|
|
47
|
+
* TDSnap Inflector buttons and Grid3 Suffix buttons are grammar controls,
|
|
48
|
+
* not content words — they should NOT auto-enable morphology.
|
|
45
49
|
*/
|
|
46
50
|
private treeHasPosTags;
|
|
47
51
|
/**
|
|
@@ -53,6 +57,35 @@ export declare class MetricsCalculator {
|
|
|
53
57
|
* before calculateWordFormMetrics assigns effort to each form.
|
|
54
58
|
*/
|
|
55
59
|
private expandMorphologicalPredictions;
|
|
60
|
+
/**
|
|
61
|
+
* Expand morphological predictions for Grid3 pagesets.
|
|
62
|
+
*
|
|
63
|
+
* Grid3 uses suffix buttons (pos='Suffix') on the same page as content words.
|
|
64
|
+
* Different pages have different suffix buttons — e.g., topic pages may only
|
|
65
|
+
* have -s (plural), while the Magic Wand page has -s, -er, -est, -ly, -y, -'s.
|
|
66
|
+
*
|
|
67
|
+
* Rules:
|
|
68
|
+
* 1. Build a suffix→formSlot map (-s → plural, -er → comparative, etc.)
|
|
69
|
+
* 2. For each page, collect available suffix buttons
|
|
70
|
+
* 3. Only generate forms for slots that have matching suffix buttons on that page
|
|
71
|
+
* 4. POS inference is used for untagged content words (Grid3 grids often lack POS)
|
|
72
|
+
*/
|
|
73
|
+
private expandGrid3Predictions;
|
|
74
|
+
/**
|
|
75
|
+
* Expand morphological predictions for TDSnap pagesets.
|
|
76
|
+
*
|
|
77
|
+
* TDSnap uses Inflector buttons (ContentType=3) on "Word Forms" pages to
|
|
78
|
+
* provide morphology. These pages are loaded dynamically by the runtime,
|
|
79
|
+
* NOT via navigation buttons, so they are unreachable in our tree model.
|
|
80
|
+
*
|
|
81
|
+
* Rules:
|
|
82
|
+
* 1. If the pageset has NO Inflector buttons → no morphology at all
|
|
83
|
+
* 2. Only generate forms whose grammar tag matches an available Inflector
|
|
84
|
+
* (e.g., if there's no -ly Inflector, don't generate "happily")
|
|
85
|
+
* 3. No POS inference — only the lexicon determines which words get forms
|
|
86
|
+
*/
|
|
87
|
+
private expandTDSnapPredictions;
|
|
88
|
+
private filterFormsByAvailableTags;
|
|
56
89
|
/**
|
|
57
90
|
* Calculate metrics for word forms (smart grammar predictions)
|
|
58
91
|
*
|