@willwade/aac-processors 0.0.12 → 0.0.14

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.
Files changed (60) hide show
  1. package/README.md +46 -44
  2. package/dist/core/baseProcessor.d.ts +41 -0
  3. package/dist/core/baseProcessor.js +41 -0
  4. package/dist/core/treeStructure.d.ts +35 -2
  5. package/dist/core/treeStructure.js +18 -3
  6. package/dist/index.d.ts +2 -2
  7. package/dist/index.js +2 -2
  8. package/dist/processors/astericsGridProcessor.d.ts +15 -0
  9. package/dist/processors/astericsGridProcessor.js +17 -0
  10. package/dist/processors/gridset/helpers.d.ts +4 -1
  11. package/dist/processors/gridset/helpers.js +4 -0
  12. package/dist/processors/gridset/pluginTypes.js +51 -50
  13. package/dist/processors/gridset/symbolAlignment.d.ts +125 -0
  14. package/dist/processors/gridset/symbolAlignment.js +283 -0
  15. package/dist/processors/gridset/symbolExtractor.js +3 -2
  16. package/dist/processors/gridset/symbolSearch.js +9 -7
  17. package/dist/processors/gridsetProcessor.d.ts +26 -0
  18. package/dist/processors/gridsetProcessor.js +178 -25
  19. package/dist/processors/obfProcessor.d.ts +26 -0
  20. package/dist/processors/obfProcessor.js +94 -1
  21. package/dist/processors/snap/helpers.d.ts +5 -1
  22. package/dist/processors/snap/helpers.js +5 -0
  23. package/dist/processors/snapProcessor.d.ts +2 -0
  24. package/dist/processors/snapProcessor.js +156 -5
  25. package/dist/processors/touchchatProcessor.d.ts +26 -0
  26. package/dist/processors/touchchatProcessor.js +106 -6
  27. package/dist/types/aac.d.ts +63 -0
  28. package/dist/types/aac.js +33 -0
  29. package/dist/{optional → utilities}/analytics/history.d.ts +12 -1
  30. package/dist/{optional → utilities}/analytics/index.d.ts +2 -0
  31. package/dist/{optional → utilities}/analytics/index.js +6 -1
  32. package/dist/{optional → utilities}/analytics/metrics/comparison.js +8 -4
  33. package/dist/{optional → utilities}/analytics/metrics/core.d.ts +9 -0
  34. package/dist/{optional → utilities}/analytics/metrics/core.js +190 -37
  35. package/dist/{optional → utilities}/analytics/metrics/effort.d.ts +10 -0
  36. package/dist/{optional → utilities}/analytics/metrics/effort.js +13 -0
  37. package/dist/utilities/analytics/metrics/obl-types.d.ts +93 -0
  38. package/dist/utilities/analytics/metrics/obl-types.js +7 -0
  39. package/dist/utilities/analytics/metrics/obl.d.ts +40 -0
  40. package/dist/utilities/analytics/metrics/obl.js +287 -0
  41. package/dist/{optional → utilities}/analytics/metrics/vocabulary.js +6 -4
  42. package/dist/{optional → utilities}/symbolTools.js +13 -16
  43. package/dist/utilities/translation/translationProcessor.d.ts +119 -0
  44. package/dist/utilities/translation/translationProcessor.js +204 -0
  45. package/dist/validation/gridsetValidator.js +10 -0
  46. package/package.json +1 -1
  47. /package/dist/{optional → utilities}/analytics/history.js +0 -0
  48. /package/dist/{optional → utilities}/analytics/metrics/comparison.d.ts +0 -0
  49. /package/dist/{optional → utilities}/analytics/metrics/index.d.ts +0 -0
  50. /package/dist/{optional → utilities}/analytics/metrics/index.js +0 -0
  51. /package/dist/{optional → utilities}/analytics/metrics/sentence.d.ts +0 -0
  52. /package/dist/{optional → utilities}/analytics/metrics/sentence.js +0 -0
  53. /package/dist/{optional → utilities}/analytics/metrics/types.d.ts +0 -0
  54. /package/dist/{optional → utilities}/analytics/metrics/types.js +0 -0
  55. /package/dist/{optional → utilities}/analytics/metrics/vocabulary.d.ts +0 -0
  56. /package/dist/{optional → utilities}/analytics/reference/index.d.ts +0 -0
  57. /package/dist/{optional → utilities}/analytics/reference/index.js +0 -0
  58. /package/dist/{optional → utilities}/analytics/utils/idGenerator.d.ts +0 -0
  59. /package/dist/{optional → utilities}/analytics/utils/idGenerator.js +0 -0
  60. /package/dist/{optional → utilities}/symbolTools.d.ts +0 -0
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.SnapProcessor = void 0;
7
7
  const baseProcessor_1 = require("../core/baseProcessor");
8
8
  const treeStructure_1 = require("../core/treeStructure");
9
- const idGenerator_1 = require("../optional/analytics/utils/idGenerator");
9
+ const idGenerator_1 = require("../utilities/analytics/utils/idGenerator");
10
10
  const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
11
11
  const path_1 = __importDefault(require("path"));
12
12
  const fs_1 = __importDefault(require("fs"));
@@ -17,8 +17,11 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
17
17
  super(options);
18
18
  this.symbolResolver = null;
19
19
  this.loadAudio = false;
20
+ this.pageLayoutPreference = 'scanning'; // Default to scanning for metrics
20
21
  this.symbolResolver = symbolResolver;
21
22
  this.loadAudio = options.loadAudio !== undefined ? options.loadAudio : true;
23
+ this.pageLayoutPreference =
24
+ options.pageLayoutPreference !== undefined ? options.pageLayoutPreference : 'scanning'; // Default to scanning
22
25
  }
23
26
  extractTexts(filePathOrBuffer) {
24
27
  const tree = this.loadIntoTree(filePathOrBuffer);
@@ -79,11 +82,127 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
79
82
  });
80
83
  tree.addPage(page);
81
84
  });
85
+ const scanGroupsByPageLayout = new Map();
86
+ try {
87
+ const scanGroupRows = db
88
+ .prepare('SELECT Id, SerializedGridPositions, PageLayoutId FROM ScanGroup ORDER BY Id')
89
+ .all();
90
+ if (scanGroupRows && scanGroupRows.length > 0) {
91
+ // Group by PageLayoutId first
92
+ const groupsByLayout = new Map();
93
+ scanGroupRows.forEach((sg) => {
94
+ if (!groupsByLayout.has(sg.PageLayoutId)) {
95
+ groupsByLayout.set(sg.PageLayoutId, []);
96
+ }
97
+ const layoutGroups = groupsByLayout.get(sg.PageLayoutId);
98
+ if (layoutGroups) {
99
+ layoutGroups.push(sg);
100
+ }
101
+ });
102
+ // For each PageLayout, assign scan block numbers based on order (1-based index)
103
+ groupsByLayout.forEach((groups, layoutId) => {
104
+ groups.forEach((sg, index) => {
105
+ // Parse SerializedGridPositions JSON
106
+ let positions = [];
107
+ try {
108
+ positions = JSON.parse(sg.SerializedGridPositions);
109
+ }
110
+ catch (e) {
111
+ // Invalid JSON, skip this group
112
+ return;
113
+ }
114
+ const scanGroup = {
115
+ id: sg.Id,
116
+ scanBlock: index + 1, // Scan block is 1-based index
117
+ positions: positions,
118
+ };
119
+ if (!scanGroupsByPageLayout.has(layoutId)) {
120
+ scanGroupsByPageLayout.set(layoutId, []);
121
+ }
122
+ const layoutGroups = scanGroupsByPageLayout.get(layoutId);
123
+ if (layoutGroups) {
124
+ layoutGroups.push(scanGroup);
125
+ }
126
+ });
127
+ });
128
+ }
129
+ }
130
+ catch (e) {
131
+ // No ScanGroups table or error loading, continue without scan blocks
132
+ console.warn('[SnapProcessor] Failed to load ScanGroups:', e);
133
+ }
82
134
  // Load buttons per page, using UniqueId for page id
83
135
  for (const pageRow of pages) {
84
- let buttons = [];
85
136
  // Create a map to track page grid layouts
86
137
  const pageGrids = new Map();
138
+ // Select PageLayout for this page based on preference
139
+ let selectedPageLayoutId = null;
140
+ try {
141
+ const pageLayouts = db
142
+ .prepare('SELECT Id, PageLayoutSetting FROM PageLayout WHERE PageId = ?')
143
+ .all(pageRow.Id);
144
+ if (pageLayouts && pageLayouts.length > 0) {
145
+ // Parse PageLayoutSetting: "columns,rows,hasScanGroups,?"
146
+ const layoutsWithInfo = pageLayouts.map((pl) => {
147
+ const parts = pl.PageLayoutSetting.split(',');
148
+ const cols = parseInt(parts[0], 10) || 0;
149
+ const rows = parseInt(parts[1], 10) || 0;
150
+ const hasScanning = parts[2] === 'True';
151
+ const size = cols * rows;
152
+ return { id: pl.Id, cols, rows, size, hasScanning };
153
+ });
154
+ // Select based on preference
155
+ if (typeof this.pageLayoutPreference === 'number') {
156
+ // Specific PageLayoutId
157
+ selectedPageLayoutId = this.pageLayoutPreference;
158
+ }
159
+ else if (this.pageLayoutPreference === 'largest') {
160
+ // Select layout with largest grid size, prefer layouts with ScanGroups
161
+ layoutsWithInfo.sort((a, b) => {
162
+ const sizeDiff = b.size - a.size;
163
+ if (sizeDiff !== 0)
164
+ return sizeDiff;
165
+ // Same size, prefer one with ScanGroups
166
+ const aHasScanning = scanGroupsByPageLayout.has(a.id);
167
+ const bHasScanning = scanGroupsByPageLayout.has(b.id);
168
+ return (bHasScanning ? 1 : 0) - (aHasScanning ? 1 : 0);
169
+ });
170
+ selectedPageLayoutId = layoutsWithInfo[0].id;
171
+ }
172
+ else if (this.pageLayoutPreference === 'smallest') {
173
+ // Select layout with smallest grid size, prefer layouts with ScanGroups
174
+ layoutsWithInfo.sort((a, b) => {
175
+ const sizeDiff = a.size - b.size;
176
+ if (sizeDiff !== 0)
177
+ return sizeDiff;
178
+ // Same size, prefer one with ScanGroups
179
+ const aHasScanning = scanGroupsByPageLayout.has(a.id);
180
+ const bHasScanning = scanGroupsByPageLayout.has(b.id);
181
+ return (bHasScanning ? 1 : 0) - (aHasScanning ? 1 : 0);
182
+ });
183
+ selectedPageLayoutId = layoutsWithInfo[0].id;
184
+ }
185
+ else if (this.pageLayoutPreference === 'scanning') {
186
+ // Select layout with scanning enabled (check against actual ScanGroups)
187
+ const scanningLayouts = layoutsWithInfo.filter((l) => scanGroupsByPageLayout.has(l.id));
188
+ if (scanningLayouts.length > 0) {
189
+ scanningLayouts.sort((a, b) => b.size - a.size);
190
+ selectedPageLayoutId = scanningLayouts[0].id;
191
+ }
192
+ else {
193
+ // Fallback to largest
194
+ layoutsWithInfo.sort((a, b) => b.size - a.size);
195
+ selectedPageLayoutId = layoutsWithInfo[0].id;
196
+ }
197
+ }
198
+ }
199
+ }
200
+ catch (e) {
201
+ // Error selecting PageLayout, will load all buttons
202
+ console.warn(`[SnapProcessor] Failed to select PageLayout for page ${pageRow.Id}:`, e);
203
+ }
204
+ // Load buttons
205
+ let buttons = [];
87
206
  try {
88
207
  const buttonColumns = getTableColumns('Button');
89
208
  const selectFields = [
@@ -112,15 +231,19 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
112
231
  ? 'b.SerializedMessageSoundMetadata'
113
232
  : 'NULL AS SerializedMessageSoundMetadata');
114
233
  }
115
- selectFields.push('ep.GridPosition', 'er.PageId as ButtonPageId');
234
+ const placementColumns = getTableColumns('ElementPlacement');
235
+ selectFields.push(placementColumns.has('GridPosition') ? 'ep.GridPosition' : 'NULL AS GridPosition', placementColumns.has('PageLayoutId') ? 'ep.PageLayoutId' : 'NULL AS PageLayoutId', 'er.PageId as ButtonPageId');
116
236
  const buttonQuery = `
117
237
  SELECT ${selectFields.join(', ')}
118
238
  FROM Button b
119
239
  INNER JOIN ElementReference er ON b.ElementReferenceId = er.Id
120
240
  LEFT JOIN ElementPlacement ep ON ep.ElementReferenceId = er.Id
121
- WHERE er.PageId = ?
241
+ WHERE er.PageId = ? ${selectedPageLayoutId ? 'AND ep.PageLayoutId = ?' : ''}
122
242
  `;
123
- buttons = db.prepare(buttonQuery).all(pageRow.Id);
243
+ const queryParams = selectedPageLayoutId
244
+ ? [pageRow.Id, selectedPageLayoutId]
245
+ : [pageRow.Id];
246
+ buttons = db.prepare(buttonQuery).all(...queryParams);
124
247
  }
125
248
  catch (err) {
126
249
  const errorMessage = err instanceof Error ? err.message : String(err);
@@ -252,6 +375,34 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
252
375
  const [xStr, yStr] = gridPositionStr.split(',');
253
376
  const gridX = parseInt(xStr, 10);
254
377
  const gridY = parseInt(yStr, 10);
378
+ // Set button x,y properties (critical for metrics!)
379
+ if (!isNaN(gridX) && !isNaN(gridY)) {
380
+ button.x = gridX;
381
+ button.y = gridY;
382
+ // Determine scan block from ScanGroups (TD Snap "Group Scan")
383
+ // IMPORTANT: Only match against ScanGroups from the SAME PageLayout
384
+ // A button can exist in multiple layouts with different positions
385
+ const buttonPageLayoutId = btnRow.PageLayoutId;
386
+ if (buttonPageLayoutId && scanGroupsByPageLayout.has(buttonPageLayoutId)) {
387
+ const scanGroups = scanGroupsByPageLayout.get(buttonPageLayoutId);
388
+ if (scanGroups && scanGroups.length > 0) {
389
+ // Find which ScanGroup contains this button's position
390
+ for (const scanGroup of scanGroups) {
391
+ // Skip if positions array is null or undefined
392
+ if (!scanGroup.positions || !Array.isArray(scanGroup.positions)) {
393
+ continue;
394
+ }
395
+ const foundInGroup = scanGroup.positions.some((pos) => pos.Column === gridX && pos.Row === gridY);
396
+ if (foundInGroup) {
397
+ // Use the scan block number from the ScanGroup
398
+ // ScanGroup scanBlock is already 1-based (index + 1)
399
+ button.scanBlock = scanGroup.scanBlock;
400
+ break; // Found the scan block, stop looking
401
+ }
402
+ }
403
+ }
404
+ }
405
+ }
255
406
  // Place button in grid if within bounds and coordinates are valid
256
407
  if (!isNaN(gridX) &&
257
408
  !isNaN(gridY) &&
@@ -1,6 +1,7 @@
1
1
  import { BaseProcessor, ProcessorOptions, ExtractStringsResult, TranslatedString, SourceString } from '../core/baseProcessor';
2
2
  import { AACTree } from '../core/treeStructure';
3
3
  import { ValidationResult } from '../validation/validationTypes';
4
+ import { type ButtonForTranslation, type LLMLTranslationResult } from '../utilities/translation/translationProcessor';
4
5
  declare class TouchChatProcessor extends BaseProcessor {
5
6
  private tree;
6
7
  private sourceFile;
@@ -30,5 +31,30 @@ declare class TouchChatProcessor extends BaseProcessor {
30
31
  * @returns Promise with validation result
31
32
  */
32
33
  validate(filePath: string): Promise<ValidationResult>;
34
+ /**
35
+ * Extract symbol information from a TouchChat file for LLM-based translation.
36
+ * Returns a structured format showing which buttons have symbols and their context.
37
+ *
38
+ * This method uses shared translation utilities that work across all AAC formats.
39
+ *
40
+ * @param filePathOrBuffer - Path to TouchChat .ce file or buffer
41
+ * @returns Array of symbol information for LLM processing
42
+ */
43
+ extractSymbolsForLLM(filePathOrBuffer: string | Buffer): ButtonForTranslation[];
44
+ /**
45
+ * Apply LLM translations with symbol information.
46
+ * The LLM should provide translations with symbol attachments in the correct positions.
47
+ *
48
+ * This method uses shared translation utilities that work across all AAC formats.
49
+ *
50
+ * @param filePathOrBuffer - Path to TouchChat .ce file or buffer
51
+ * @param llmTranslations - Array of LLM translations with symbol info
52
+ * @param outputPath - Where to save the translated TouchChat file
53
+ * @param options - Translation options (e.g., allowPartial for testing)
54
+ * @returns Buffer of the translated TouchChat file
55
+ */
56
+ processLLMTranslations(filePathOrBuffer: string | Buffer, llmTranslations: LLMLTranslationResult[], outputPath: string, options?: {
57
+ allowPartial?: boolean;
58
+ }): Buffer;
33
59
  }
34
60
  export { TouchChatProcessor };
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.TouchChatProcessor = void 0;
7
7
  const baseProcessor_1 = require("../core/baseProcessor");
8
8
  const treeStructure_1 = require("../core/treeStructure");
9
- const idGenerator_1 = require("../optional/analytics/utils/idGenerator");
9
+ const idGenerator_1 = require("../utilities/analytics/utils/idGenerator");
10
10
  const stringCasing_1 = require("../core/stringCasing");
11
11
  const adm_zip_1 = __importDefault(require("adm-zip"));
12
12
  const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
@@ -14,6 +14,7 @@ const path_1 = __importDefault(require("path"));
14
14
  const fs_1 = __importDefault(require("fs"));
15
15
  const os_1 = __importDefault(require("os"));
16
16
  const touchChatValidator_1 = require("../validation/touchChatValidator");
17
+ const translationProcessor_1 = require("../utilities/translation/translationProcessor");
17
18
  const toNumberOrUndefined = (value) => typeof value === 'number' ? value : undefined;
18
19
  const toStringOrUndefined = (value) => typeof value === 'string' && value.length > 0 ? value : undefined;
19
20
  const toBooleanOrUndefined = (value) => typeof value === 'number' ? value !== 0 : undefined;
@@ -24,6 +25,17 @@ function intToHex(colorInt) {
24
25
  // Assuming the color is in ARGB format, we mask out the alpha channel
25
26
  return `#${(colorInt & 0x00ffffff).toString(16).padStart(6, '0')}`;
26
27
  }
28
+ /**
29
+ * Map TouchChat visible value to AAC standard visibility
30
+ * TouchChat: 0 = Hidden, 1 = Visible
31
+ * Maps to: 'Hidden' | 'Visible' | undefined
32
+ */
33
+ function mapTouchChatVisibility(visible) {
34
+ if (visible === null || visible === undefined) {
35
+ return undefined; // Default to visible
36
+ }
37
+ return visible === 0 ? 'Hidden' : 'Visible';
38
+ }
27
39
  class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
28
40
  constructor(options) {
29
41
  super(options);
@@ -132,7 +144,7 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
132
144
  });
133
145
  // Load button boxes and their cells
134
146
  const buttonBoxQuery = `
135
- SELECT bbc.*, b.*, bb.id as box_id
147
+ SELECT bbc.*, b.*, bb.id as box_id
136
148
  FROM button_box_cells bbc
137
149
  JOIN buttons b ON b.resource_id = bbc.resource_id
138
150
  JOIN button_boxes bb ON bb.id = bbc.button_box_id
@@ -166,7 +178,11 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
166
178
  label: cell.label || '',
167
179
  message: cell.message || '',
168
180
  semanticAction: semanticAction,
169
- semantic_id: cell.symbol_link_id || cell.symbolLinkId || undefined, // Extract semantic_id from symbol_link_id
181
+ semantic_id: (cell.symbol_link_id || cell.symbolLinkId) || undefined, // Extract semantic_id from symbol_link_id
182
+ visibility: mapTouchChatVisibility(cell.visible || undefined),
183
+ // Note: TouchChat does not use scan blocks in the file
184
+ // Scanning is a runtime feature (linear/row-column patterns)
185
+ // scanBlock defaults to 1 (no grouping)
170
186
  style: {
171
187
  backgroundColor: intToHex(style?.body_color),
172
188
  borderColor: intToHex(style?.border_color),
@@ -183,9 +199,9 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
183
199
  });
184
200
  buttonBoxes.get(cell.box_id)?.push({
185
201
  button,
186
- location: cell.location,
187
- spanX: cell.span_x,
188
- spanY: cell.span_y,
202
+ location: cell.location || 0,
203
+ spanX: cell.span_x || 1,
204
+ spanY: cell.span_y || 1,
189
205
  });
190
206
  });
191
207
  // Map button boxes to pages
@@ -306,6 +322,10 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
306
322
  label: btnRow.label || '',
307
323
  message: btnRow.message || '',
308
324
  semanticAction: semanticAction,
325
+ visibility: mapTouchChatVisibility(btnRow.visible),
326
+ // Note: TouchChat does not use scan blocks in the file
327
+ // Scanning is a runtime feature (linear/row-column patterns)
328
+ // scanBlock defaults to 1 (no grouping)
309
329
  style: {
310
330
  backgroundColor: intToHex(style?.body_color),
311
331
  borderColor: intToHex(style?.border_color),
@@ -823,5 +843,85 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
823
843
  async validate(filePath) {
824
844
  return touchChatValidator_1.TouchChatValidator.validateFile(filePath);
825
845
  }
846
+ /**
847
+ * Extract symbol information from a TouchChat file for LLM-based translation.
848
+ * Returns a structured format showing which buttons have symbols and their context.
849
+ *
850
+ * This method uses shared translation utilities that work across all AAC formats.
851
+ *
852
+ * @param filePathOrBuffer - Path to TouchChat .ce file or buffer
853
+ * @returns Array of symbol information for LLM processing
854
+ */
855
+ extractSymbolsForLLM(filePathOrBuffer) {
856
+ const tree = this.loadIntoTree(filePathOrBuffer);
857
+ // Collect all buttons from all pages
858
+ const allButtons = [];
859
+ Object.values(tree.pages).forEach((page) => {
860
+ page.buttons.forEach((button) => {
861
+ // Add page context to each button
862
+ button.pageId = page.id;
863
+ button.pageName = page.name || page.id;
864
+ allButtons.push(button);
865
+ });
866
+ });
867
+ // Use shared utility to extract buttons with translation context
868
+ return (0, translationProcessor_1.extractAllButtonsForTranslation)(allButtons, (button) => ({
869
+ pageId: button.pageId,
870
+ pageName: button.pageName,
871
+ }));
872
+ }
873
+ /**
874
+ * Apply LLM translations with symbol information.
875
+ * The LLM should provide translations with symbol attachments in the correct positions.
876
+ *
877
+ * This method uses shared translation utilities that work across all AAC formats.
878
+ *
879
+ * @param filePathOrBuffer - Path to TouchChat .ce file or buffer
880
+ * @param llmTranslations - Array of LLM translations with symbol info
881
+ * @param outputPath - Where to save the translated TouchChat file
882
+ * @param options - Translation options (e.g., allowPartial for testing)
883
+ * @returns Buffer of the translated TouchChat file
884
+ */
885
+ processLLMTranslations(filePathOrBuffer, llmTranslations, outputPath, options) {
886
+ const tree = this.loadIntoTree(filePathOrBuffer);
887
+ // Validate translations using shared utility
888
+ const buttonIds = Object.values(tree.pages).flatMap((page) => page.buttons.map((b) => b.id));
889
+ (0, translationProcessor_1.validateTranslationResults)(llmTranslations, buttonIds, options);
890
+ // Create a map for quick lookup
891
+ const translationMap = new Map(llmTranslations.map((t) => [t.buttonId, t]));
892
+ // Apply translations
893
+ Object.values(tree.pages).forEach((page) => {
894
+ page.buttons.forEach((button) => {
895
+ const translation = translationMap.get(button.id);
896
+ if (!translation)
897
+ return;
898
+ // Apply label translation
899
+ if (translation.translatedLabel) {
900
+ button.label = translation.translatedLabel;
901
+ }
902
+ // Apply message translation
903
+ if (translation.translatedMessage) {
904
+ button.message = translation.translatedMessage;
905
+ // Update semantic action if symbols provided
906
+ if (translation.symbols && translation.symbols.length > 0) {
907
+ if (!button.semanticAction) {
908
+ button.semanticAction = {
909
+ category: treeStructure_1.AACSemanticCategory.COMMUNICATION,
910
+ intent: treeStructure_1.AACSemanticIntent.SPEAK_TEXT,
911
+ text: translation.translatedMessage,
912
+ };
913
+ }
914
+ button.semanticAction.richText = {
915
+ text: translation.translatedMessage,
916
+ symbols: translation.symbols,
917
+ };
918
+ }
919
+ }
920
+ });
921
+ });
922
+ // Save and return
923
+ this.saveFromTree(tree, outputPath);
924
+ return fs_1.default.readFileSync(outputPath);
925
+ }
826
926
  }
827
927
  exports.TouchChatProcessor = TouchChatProcessor;
@@ -1,4 +1,54 @@
1
1
  import { AACSemanticAction } from '../core/treeStructure';
2
+ /**
3
+ * Scanning selection methods for switch access
4
+ * Determines how the scanning advances through items
5
+ */
6
+ export declare enum ScanningSelectionMethod {
7
+ /** Automatically advance through items at timed intervals */
8
+ AutoScan = "AutoScan",
9
+ /** Automatic scanning with overscan (two-stage scanning) */
10
+ AutoScanWithOverscan = "AutoScanWithOverscan",
11
+ /** Hold switch to advance, release to select */
12
+ HoldToAdvance = "HoldToAdvance",
13
+ /** Hold to advance with overscan */
14
+ HoldToAdvanceWithOverscan = "HoldToAdvanceWithOverscan",
15
+ /** Tap switch to advance, tap again to select */
16
+ TapToAdvance = "TapToAdvance"
17
+ }
18
+ /**
19
+ * Cell scanning order patterns
20
+ * Determines the sequence in which cells are highlighted
21
+ */
22
+ export declare enum CellScanningOrder {
23
+ /** Simple linear scan across rows (left-to-right, top-to-bottom) */
24
+ SimpleScan = "SimpleScan",
25
+ /** Simple linear scan down columns (top-to-bottom, left-to-right) */
26
+ SimpleScanColumnsFirst = "SimpleScanColumnsFirst",
27
+ /** Row-group scanning: highlight rows first, then cells within selected row */
28
+ RowColumnScan = "RowColumnScan",
29
+ /** Column-group scanning: highlight columns first, then cells within selected column */
30
+ ColumnRowScan = "ColumnRowScan"
31
+ }
32
+ /**
33
+ * Scanning configuration for a page or pageset
34
+ * Controls how switch scanning operates
35
+ */
36
+ export interface ScanningConfig {
37
+ /** Method for advancing through items */
38
+ selectionMethod?: ScanningSelectionMethod;
39
+ /** Order in which cells are scanned */
40
+ cellScanningOrder?: CellScanningOrder;
41
+ /** Whether block scanning is enabled (group cells by scanBlock number) */
42
+ blockScanEnabled?: boolean;
43
+ /** Whether to include the workspace/message bar in scanning */
44
+ scanWorkspace?: boolean;
45
+ /** Time in milliseconds to highlight each item */
46
+ forwardScanSpeed?: number;
47
+ /** Time in milliseconds to wait before auto-accepting selection */
48
+ dwellTime?: number;
49
+ /** How the selection is accepted */
50
+ acceptScanMethod?: 'Switch' | 'Timeout' | 'Hold';
51
+ }
2
52
  export interface AACStyle {
3
53
  backgroundColor?: string;
4
54
  fontColor?: string;
@@ -35,7 +85,18 @@ export interface AACButton {
35
85
  y?: number;
36
86
  columnSpan?: number;
37
87
  rowSpan?: number;
88
+ /**
89
+ * Scan block number (1-8) for block scanning
90
+ * Buttons with the same scanBlock number are highlighted together
91
+ * @deprecated Use scanBlock instead (singular, not array)
92
+ */
38
93
  scanBlocks?: number[];
94
+ /**
95
+ * Scan block number (1-8) for block scanning
96
+ * Buttons with the same scanBlock number are highlighted together
97
+ * Reduces scanning effort by grouping buttons
98
+ */
99
+ scanBlock?: number;
39
100
  visibility?: 'Visible' | 'Hidden' | 'Disabled' | 'PointerAndTouchOnly' | 'Empty';
40
101
  directActivate?: boolean;
41
102
  audioDescription?: string;
@@ -58,6 +119,8 @@ export interface AACPage {
58
119
  sounds?: any[];
59
120
  semantic_ids?: string[];
60
121
  clone_ids?: string[];
122
+ scanningConfig?: ScanningConfig;
123
+ scanBlocksConfig?: any[];
61
124
  }
62
125
  export interface AACTree {
63
126
  pages: {
package/dist/types/aac.js CHANGED
@@ -1,2 +1,35 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CellScanningOrder = exports.ScanningSelectionMethod = void 0;
4
+ /**
5
+ * Scanning selection methods for switch access
6
+ * Determines how the scanning advances through items
7
+ */
8
+ var ScanningSelectionMethod;
9
+ (function (ScanningSelectionMethod) {
10
+ /** Automatically advance through items at timed intervals */
11
+ ScanningSelectionMethod["AutoScan"] = "AutoScan";
12
+ /** Automatic scanning with overscan (two-stage scanning) */
13
+ ScanningSelectionMethod["AutoScanWithOverscan"] = "AutoScanWithOverscan";
14
+ /** Hold switch to advance, release to select */
15
+ ScanningSelectionMethod["HoldToAdvance"] = "HoldToAdvance";
16
+ /** Hold to advance with overscan */
17
+ ScanningSelectionMethod["HoldToAdvanceWithOverscan"] = "HoldToAdvanceWithOverscan";
18
+ /** Tap switch to advance, tap again to select */
19
+ ScanningSelectionMethod["TapToAdvance"] = "TapToAdvance";
20
+ })(ScanningSelectionMethod || (exports.ScanningSelectionMethod = ScanningSelectionMethod = {}));
21
+ /**
22
+ * Cell scanning order patterns
23
+ * Determines the sequence in which cells are highlighted
24
+ */
25
+ var CellScanningOrder;
26
+ (function (CellScanningOrder) {
27
+ /** Simple linear scan across rows (left-to-right, top-to-bottom) */
28
+ CellScanningOrder["SimpleScan"] = "SimpleScan";
29
+ /** Simple linear scan down columns (top-to-bottom, left-to-right) */
30
+ CellScanningOrder["SimpleScanColumnsFirst"] = "SimpleScanColumnsFirst";
31
+ /** Row-group scanning: highlight rows first, then cells within selected row */
32
+ CellScanningOrder["RowColumnScan"] = "RowColumnScan";
33
+ /** Column-group scanning: highlight columns first, then cells within selected column */
34
+ CellScanningOrder["ColumnRowScan"] = "ColumnRowScan";
35
+ })(CellScanningOrder || (exports.CellScanningOrder = CellScanningOrder = {}));
@@ -1,7 +1,8 @@
1
1
  import { dotNetTicksToDate } from '../../utils/dotnetTicks';
2
2
  import { Grid3UserPath } from '../../processors/gridset/helpers';
3
3
  import { SnapUserInfo } from '../../processors/snap/helpers';
4
- export type HistorySource = 'Grid' | 'Snap';
4
+ import { AACSemanticCategory, AACSemanticIntent } from '../../core/treeStructure';
5
+ export type HistorySource = 'Grid' | 'Snap' | 'OBL' | string;
5
6
  export interface HistoryOccurrence {
6
7
  timestamp: Date;
7
8
  latitude?: number | null;
@@ -9,12 +10,22 @@ export interface HistoryOccurrence {
9
10
  modeling?: boolean;
10
11
  accessMethod?: number | null;
11
12
  pageId?: string | null;
13
+ buttonId?: string | null;
14
+ boardId?: string | null;
15
+ spoken?: boolean;
16
+ vocalization?: string;
17
+ imageUrl?: string;
18
+ actions?: any[];
19
+ type?: 'button' | 'action' | 'utterance' | 'note' | 'other';
20
+ intent?: AACSemanticIntent | string;
21
+ category?: AACSemanticCategory;
12
22
  }
13
23
  export interface HistoryPlatformExtras {
14
24
  label?: string;
15
25
  message?: string;
16
26
  buttonId?: string;
17
27
  contentXml?: string;
28
+ [key: string]: any;
18
29
  }
19
30
  export interface HistoryEntry {
20
31
  id: string;
@@ -13,6 +13,8 @@ export * from './metrics/types';
13
13
  export * from './metrics/effort';
14
14
  export * from './utils/idGenerator';
15
15
  export * from './history';
16
+ export * from './metrics/obl-types';
17
+ export { OblUtil, OblAnonymizer } from './metrics/obl';
16
18
  export { MetricsCalculator } from './metrics/core';
17
19
  export { VocabularyAnalyzer } from './metrics/vocabulary';
18
20
  export { SentenceAnalyzer } from './metrics/sentence';
@@ -28,7 +28,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
28
28
  return (mod && mod.__esModule) ? mod : { "default": mod };
29
29
  };
30
30
  Object.defineProperty(exports, "__esModule", { value: true });
31
- exports.ReferenceLoader = exports.ComparisonAnalyzer = exports.SentenceAnalyzer = exports.VocabularyAnalyzer = exports.MetricsCalculator = void 0;
31
+ exports.ReferenceLoader = exports.ComparisonAnalyzer = exports.SentenceAnalyzer = exports.VocabularyAnalyzer = exports.MetricsCalculator = exports.OblAnonymizer = exports.OblUtil = void 0;
32
32
  exports.getReferenceDataPath = getReferenceDataPath;
33
33
  exports.hasReferenceData = hasReferenceData;
34
34
  const path_1 = __importDefault(require("path"));
@@ -39,6 +39,11 @@ __exportStar(require("./metrics/effort"), exports);
39
39
  __exportStar(require("./utils/idGenerator"), exports);
40
40
  // Export history functionality
41
41
  __exportStar(require("./history"), exports);
42
+ // Export OBL logging support
43
+ __exportStar(require("./metrics/obl-types"), exports);
44
+ var obl_1 = require("./metrics/obl");
45
+ Object.defineProperty(exports, "OblUtil", { enumerable: true, get: function () { return obl_1.OblUtil; } });
46
+ Object.defineProperty(exports, "OblAnonymizer", { enumerable: true, get: function () { return obl_1.OblAnonymizer; } });
42
47
  // Export core metrics calculator
43
48
  var core_1 = require("./metrics/core");
44
49
  Object.defineProperty(exports, "MetricsCalculator", { enumerable: true, get: function () { return core_1.MetricsCalculator; } });
@@ -87,13 +87,17 @@ class ComparisonAnalyzer {
87
87
  }
88
88
  });
89
89
  highEffortWords.sort((a, b) => {
90
- const diffA = targetWords.get(a).effort - (compareWords.get(a)?.effort || 0);
91
- const diffB = targetWords.get(b).effort - (compareWords.get(b)?.effort || 0);
90
+ const targetBtnA = targetWords.get(a);
91
+ const targetBtnB = targetWords.get(b);
92
+ const diffA = (targetBtnA?.effort || 0) - (compareWords.get(a)?.effort || 0);
93
+ const diffB = (targetBtnB?.effort || 0) - (compareWords.get(b)?.effort || 0);
92
94
  return diffB - diffA;
93
95
  });
94
96
  lowEffortWords.sort((a, b) => {
95
- const diffA = (compareWords.get(a)?.effort || 0) - targetWords.get(a).effort;
96
- const diffB = (compareWords.get(b)?.effort || 0) - targetWords.get(b).effort;
97
+ const targetBtnA = targetWords.get(a);
98
+ const targetBtnB = targetWords.get(b);
99
+ const diffA = (compareWords.get(a)?.effort || 0) - (targetBtnA?.effort || 0);
100
+ const diffB = (compareWords.get(b)?.effort || 0) - (targetBtnB?.effort || 0);
97
101
  return diffB - diffA;
98
102
  });
99
103
  // Sentence analysis
@@ -21,6 +21,11 @@ export declare class MetricsCalculator {
21
21
  * Build reference maps for semantic_id and clone_id frequencies
22
22
  */
23
23
  private buildReferenceMaps;
24
+ /**
25
+ * Count scan items for visual scanning effort
26
+ * When block scanning is enabled, count unique scan blocks instead of individual buttons
27
+ */
28
+ private countScanItems;
24
29
  /**
25
30
  * Analyze starting from a specific board
26
31
  */
@@ -33,4 +38,8 @@ export declare class MetricsCalculator {
33
38
  * Calculate grid dimensions from the tree
34
39
  */
35
40
  private calculateGridDimensions;
41
+ /**
42
+ * Calculate scanning steps and selections for a button based on access method
43
+ */
44
+ private calculateScanSteps;
36
45
  }