@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
@@ -10,6 +10,7 @@
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
11
  exports.MetricsCalculator = void 0;
12
12
  const treeStructure_1 = require("../../../core/treeStructure");
13
+ const aac_1 = require("../../../types/aac");
13
14
  const effort_1 = require("./effort");
14
15
  class MetricsCalculator {
15
16
  constructor() {
@@ -142,6 +143,49 @@ class MetricsCalculator {
142
143
  });
143
144
  return { setRefs, setPcts };
144
145
  }
146
+ /**
147
+ * Count scan items for visual scanning effort
148
+ * When block scanning is enabled, count unique scan blocks instead of individual buttons
149
+ */
150
+ countScanItems(board, currentRowIndex, currentColIndex, priorScanBlocks, blockScanEnabled) {
151
+ if (!blockScanEnabled) {
152
+ // Linear scanning: count all buttons before current position
153
+ let count = 0;
154
+ for (let r = 0; r <= currentRowIndex; r++) {
155
+ const row = board.grid[r];
156
+ if (!row)
157
+ continue;
158
+ for (let c = 0; c < row.length; c++) {
159
+ if (r === currentRowIndex && c === currentColIndex)
160
+ return count;
161
+ const btn = row[c];
162
+ if (btn && (btn.label || btn.id).length > 0) {
163
+ count++;
164
+ }
165
+ }
166
+ }
167
+ return count;
168
+ }
169
+ // Block scanning: count unique scan blocks before current position
170
+ const seenBlocks = new Set();
171
+ for (let r = 0; r <= currentRowIndex; r++) {
172
+ const row = board.grid[r];
173
+ if (!row)
174
+ continue;
175
+ for (let c = 0; c < row.length; c++) {
176
+ if (r === currentRowIndex && c === currentColIndex)
177
+ return seenBlocks.size;
178
+ const btn = row[c];
179
+ if (btn && (btn.label || btn.id).length > 0) {
180
+ const block = btn.scanBlock ||
181
+ (btn.scanBlocks && btn.scanBlocks.length > 0 ? btn.scanBlocks[0] : null);
182
+ if (block !== null)
183
+ seenBlocks.add(block);
184
+ }
185
+ }
186
+ }
187
+ return seenBlocks.size;
188
+ }
145
189
  /**
146
190
  * Analyze starting from a specific board
147
191
  */
@@ -159,6 +203,8 @@ class MetricsCalculator {
159
203
  const levels = {};
160
204
  while (toVisit.length > 0) {
161
205
  const item = toVisit.shift();
206
+ if (!item)
207
+ break;
162
208
  const { board, level, entryX, entryY, priorEffort = 0, temporaryHomeId } = item;
163
209
  // Skip if already visited at a lower level with equal or better prior effort
164
210
  // Skip if already visited at a strictly lower level
@@ -191,8 +237,10 @@ class MetricsCalculator {
191
237
  boardEffort = Math.max(0, boardEffort - reuseDiscount);
192
238
  // Calculate board link percentages
193
239
  const boardPcts = this.calculateBoardLinkPercentages(tree, board);
240
+ // Get scanning configuration from page (if available)
241
+ const blockScanEnabled = board.scanningConfig?.blockScanEnabled || false;
194
242
  // Process each button
195
- let priorButtons = 0;
243
+ const priorScanBlocks = new Set();
196
244
  const btnHeight = 1.0 / rows;
197
245
  const btnWidth = 1.0 / cols;
198
246
  board.grid.forEach((row, rowIndex) => {
@@ -204,6 +252,9 @@ class MetricsCalculator {
204
252
  }
205
253
  const x = btnWidth / 2 + btnWidth * colIndex;
206
254
  const y = btnHeight / 2 + btnHeight * rowIndex;
255
+ // Calculate prior items for visual scan effort
256
+ // If block scanning enabled, count unique scan blocks instead of individual buttons
257
+ const priorItems = this.countScanItems(board, rowIndex, colIndex, priorScanBlocks, blockScanEnabled);
207
258
  // Calculate button-level effort
208
259
  let buttonEffort = boardEffort;
209
260
  // Debug for specific button (disabled for production)
@@ -240,52 +291,76 @@ class MetricsCalculator {
240
291
  boardPcts[`upstream-${btn.clone_id}`];
241
292
  buttonEffort = Math.min(buttonEffort, buttonEffort * discount);
242
293
  }
243
- // Add distance effort
244
- let distance = (0, effort_1.distanceEffort)(x, y, entryX, entryY);
245
- // Apply distance discounts
246
- if (btn.semantic_id) {
247
- if (boardPcts[btn.semantic_id]) {
294
+ // Calculate button effort based on access method (Touch vs Scanning)
295
+ const isScanning = !!board.scanningConfig || !!board.scanType;
296
+ if (isScanning) {
297
+ const { steps, selections } = this.calculateScanSteps(board, btn, rowIndex, colIndex);
298
+ let sEffort = (0, effort_1.scanningEffort)(steps, selections);
299
+ // Apply discounts to scanning effort (similar to touch)
300
+ if (btn.semantic_id && boardPcts[btn.semantic_id]) {
248
301
  const discount = effort_1.EFFORT_CONSTANTS.SAME_LOCATION_AS_PRIOR_DISCOUNT / boardPcts[btn.semantic_id];
249
- distance = Math.min(distance, distance * discount);
302
+ sEffort = Math.min(sEffort, sEffort * discount);
250
303
  }
251
- else if (boardPcts[`upstream-${btn.semantic_id}`]) {
252
- const discount = effort_1.EFFORT_CONSTANTS.RECOGNIZABLE_SEMANTIC_FROM_PRIOR_DISCOUNT /
253
- boardPcts[`upstream-${btn.semantic_id}`];
254
- distance = Math.min(distance, distance * discount);
255
- }
256
- else if (level > 0 && setPcts[btn.semantic_id]) {
257
- const discount = effort_1.EFFORT_CONSTANTS.RECOGNIZABLE_SEMANTIC_FROM_OTHER_DISCOUNT /
258
- setPcts[btn.semantic_id];
259
- distance = Math.min(distance, distance * discount);
304
+ else if (btn.clone_id && boardPcts[btn.clone_id]) {
305
+ const discount = effort_1.EFFORT_CONSTANTS.SAME_LOCATION_AS_PRIOR_DISCOUNT / boardPcts[btn.clone_id];
306
+ sEffort = Math.min(sEffort, sEffort * discount);
260
307
  }
308
+ buttonEffort += sEffort;
261
309
  }
262
- if (btn.clone_id) {
263
- if (boardPcts[btn.clone_id]) {
264
- const discount = effort_1.EFFORT_CONSTANTS.SAME_LOCATION_AS_PRIOR_DISCOUNT / boardPcts[btn.clone_id];
265
- distance = Math.min(distance, distance * discount);
310
+ else {
311
+ // Add distance effort (Touch only)
312
+ let distance = (0, effort_1.distanceEffort)(x, y, entryX, entryY);
313
+ // Apply distance discounts
314
+ if (btn.semantic_id) {
315
+ if (boardPcts[btn.semantic_id]) {
316
+ const discount = effort_1.EFFORT_CONSTANTS.SAME_LOCATION_AS_PRIOR_DISCOUNT / boardPcts[btn.semantic_id];
317
+ distance = Math.min(distance, distance * discount);
318
+ }
319
+ else if (boardPcts[`upstream-${btn.semantic_id}`]) {
320
+ const discount = effort_1.EFFORT_CONSTANTS.RECOGNIZABLE_SEMANTIC_FROM_PRIOR_DISCOUNT /
321
+ boardPcts[`upstream-${btn.semantic_id}`];
322
+ distance = Math.min(distance, distance * discount);
323
+ }
324
+ else if (level > 0 && setPcts[btn.semantic_id]) {
325
+ const discount = effort_1.EFFORT_CONSTANTS.RECOGNIZABLE_SEMANTIC_FROM_OTHER_DISCOUNT /
326
+ setPcts[btn.semantic_id];
327
+ distance = Math.min(distance, distance * discount);
328
+ }
266
329
  }
267
- else if (boardPcts[`upstream-${btn.clone_id}`]) {
268
- const discount = effort_1.EFFORT_CONSTANTS.RECOGNIZABLE_CLONE_FROM_PRIOR_DISCOUNT /
269
- boardPcts[`upstream-${btn.clone_id}`];
270
- distance = Math.min(distance, distance * discount);
330
+ if (btn.clone_id) {
331
+ if (boardPcts[btn.clone_id]) {
332
+ const discount = effort_1.EFFORT_CONSTANTS.SAME_LOCATION_AS_PRIOR_DISCOUNT / boardPcts[btn.clone_id];
333
+ distance = Math.min(distance, distance * discount);
334
+ }
335
+ else if (boardPcts[`upstream-${btn.clone_id}`]) {
336
+ const discount = effort_1.EFFORT_CONSTANTS.RECOGNIZABLE_CLONE_FROM_PRIOR_DISCOUNT /
337
+ boardPcts[`upstream-${btn.clone_id}`];
338
+ distance = Math.min(distance, distance * discount);
339
+ }
340
+ else if (level > 0 && setPcts[btn.clone_id]) {
341
+ const discount = effort_1.EFFORT_CONSTANTS.RECOGNIZABLE_CLONE_FROM_OTHER_DISCOUNT / setPcts[btn.clone_id];
342
+ distance = Math.min(distance, distance * discount);
343
+ }
271
344
  }
272
- else if (level > 0 && setPcts[btn.clone_id]) {
273
- const discount = effort_1.EFFORT_CONSTANTS.RECOGNIZABLE_CLONE_FROM_OTHER_DISCOUNT / setPcts[btn.clone_id];
274
- distance = Math.min(distance, distance * discount);
345
+ buttonEffort += distance;
346
+ // Add visual scan or local scan effort
347
+ if (distance > effort_1.EFFORT_CONSTANTS.DISTANCE_THRESHOLD_TO_SKIP_VISUAL_SCAN ||
348
+ (entryX === 1.0 && entryY === 1.0)) {
349
+ buttonEffort += (0, effort_1.visualScanEffort)(priorItems);
350
+ }
351
+ else {
352
+ buttonEffort += (0, effort_1.localScanEffort)(distance);
275
353
  }
276
- }
277
- buttonEffort += distance;
278
- // Add visual scan or local scan effort
279
- if (distance > effort_1.EFFORT_CONSTANTS.DISTANCE_THRESHOLD_TO_SKIP_VISUAL_SCAN ||
280
- (entryX === 1.0 && entryY === 1.0)) {
281
- buttonEffort += (0, effort_1.visualScanEffort)(priorButtons);
282
- }
283
- else {
284
- buttonEffort += (0, effort_1.localScanEffort)(distance);
285
354
  }
286
355
  // Add cumulative prior effort
287
356
  buttonEffort += priorEffort;
288
- priorButtons += 1;
357
+ // Track scan blocks for block scanning, otherwise track individual buttons
358
+ if (blockScanEnabled) {
359
+ const scanBlockId = btn.scanBlock ?? btn.scanBlocks?.[0];
360
+ if (scanBlockId !== undefined && scanBlockId !== null) {
361
+ priorScanBlocks.add(scanBlockId);
362
+ }
363
+ }
289
364
  // Handle navigation buttons
290
365
  if (btn.targetPageId) {
291
366
  const nextBoard = tree.getPage(btn.targetPageId);
@@ -418,5 +493,83 @@ class MetricsCalculator {
418
493
  columns: Math.round(totalCols / count),
419
494
  };
420
495
  }
496
+ /**
497
+ * Calculate scanning steps and selections for a button based on access method
498
+ */
499
+ calculateScanSteps(board, btn, rowIndex, colIndex) {
500
+ // Determine scanning type from local scanType or scanningConfig
501
+ let type = board.scanType || treeStructure_1.AACScanType.LINEAR;
502
+ if (board.scanningConfig?.cellScanningOrder) {
503
+ const order = board.scanningConfig.cellScanningOrder;
504
+ // String matching for CellScanningOrder
505
+ if (order === aac_1.CellScanningOrder.RowColumnScan)
506
+ type = treeStructure_1.AACScanType.ROW_COLUMN;
507
+ else if (order === aac_1.CellScanningOrder.ColumnRowScan)
508
+ type = treeStructure_1.AACScanType.COLUMN_ROW;
509
+ else if (order === aac_1.CellScanningOrder.SimpleScanColumnsFirst)
510
+ type = treeStructure_1.AACScanType.COLUMN_ROW;
511
+ else if (order === aac_1.CellScanningOrder.SimpleScan)
512
+ type = treeStructure_1.AACScanType.LINEAR;
513
+ }
514
+ // Force block scan if enabled in config
515
+ const isBlockScan = board.scanningConfig?.blockScanEnabled ||
516
+ type === treeStructure_1.AACScanType.BLOCK_ROW_COLUMN ||
517
+ type === treeStructure_1.AACScanType.BLOCK_COLUMN_ROW;
518
+ if (isBlockScan) {
519
+ const blockId = btn.scanBlock || (btn.scanBlocks && btn.scanBlocks.length > 0 ? btn.scanBlocks[0] : null);
520
+ // If no block assigned, treat as its own block at the end (fallback)
521
+ if (blockId === null) {
522
+ return { steps: rowIndex + colIndex + 1, selections: 1 };
523
+ }
524
+ const config = board.scanBlocksConfig?.find((c) => c.id === blockId);
525
+ const blockOrder = config?.order ?? blockId;
526
+ // Linear scan within the block
527
+ let btnInBlockIndex = 0;
528
+ let found = false;
529
+ for (let r = 0; r < board.grid.length; r++) {
530
+ for (let c = 0; c < (board.grid[r]?.length || 0); c++) {
531
+ const b = board.grid[r][c];
532
+ if (b && (b.scanBlock === blockId || b.scanBlocks?.includes(blockId))) {
533
+ if (b === btn) {
534
+ found = true;
535
+ break;
536
+ }
537
+ btnInBlockIndex++;
538
+ }
539
+ }
540
+ if (found)
541
+ break;
542
+ }
543
+ // 1 selection for block, 1 for item
544
+ return { steps: blockOrder + btnInBlockIndex + 1, selections: 2 };
545
+ }
546
+ switch (type) {
547
+ case treeStructure_1.AACScanType.LINEAR: {
548
+ let index = 0;
549
+ let found = false;
550
+ for (let r = 0; r < board.grid.length; r++) {
551
+ for (let c = 0; c < board.grid[r].length; c++) {
552
+ const b = board.grid[r][c];
553
+ if (b && (b.label || '').length > 0) {
554
+ if (b === btn) {
555
+ found = true;
556
+ break;
557
+ }
558
+ index++;
559
+ }
560
+ }
561
+ if (found)
562
+ break;
563
+ }
564
+ return { steps: index + 1, selections: 1 };
565
+ }
566
+ case treeStructure_1.AACScanType.ROW_COLUMN:
567
+ return { steps: rowIndex + 1 + (colIndex + 1), selections: 2 };
568
+ case treeStructure_1.AACScanType.COLUMN_ROW:
569
+ return { steps: colIndex + 1 + (rowIndex + 1), selections: 2 };
570
+ default:
571
+ return { steps: rowIndex + 1 + (colIndex + 1), selections: 2 };
572
+ }
573
+ }
421
574
  }
422
575
  exports.MetricsCalculator = MetricsCalculator;
@@ -27,6 +27,8 @@ export declare const EFFORT_CONSTANTS: {
27
27
  readonly RECOGNIZABLE_CLONE_FROM_PRIOR_DISCOUNT: 0.33;
28
28
  readonly RECOGNIZABLE_CLONE_FROM_OTHER_DISCOUNT: 0.33;
29
29
  readonly REUSED_CLONE_FROM_OTHER_BONUS: 0.005;
30
+ readonly SCAN_STEP_COST: 0.015;
31
+ readonly SCAN_SELECTION_COST: 0.1;
30
32
  };
31
33
  /**
32
34
  * Calculate button size effort based on grid dimensions
@@ -135,3 +137,11 @@ export declare function shouldSkipVisualScan(distance: number): boolean;
135
137
  * @returns Local scan effort
136
138
  */
137
139
  export declare function localScanEffort(distance: number): number;
140
+ /**
141
+ * Calculate effort for switch scanning
142
+ *
143
+ * @param steps - Number of scan steps to reach target
144
+ * @param selections - Number of switch selections required
145
+ * @returns Scanning effort score
146
+ */
147
+ export declare function scanningEffort(steps: number, selections: number): number;
@@ -19,6 +19,7 @@ exports.calculateButtonEffort = calculateButtonEffort;
19
19
  exports.calculateDistanceWithDiscounts = calculateDistanceWithDiscounts;
20
20
  exports.shouldSkipVisualScan = shouldSkipVisualScan;
21
21
  exports.localScanEffort = localScanEffort;
22
+ exports.scanningEffort = scanningEffort;
22
23
  /**
23
24
  * Constants for effort score calculation
24
25
  * Values match the Ruby implementation exactly
@@ -41,6 +42,8 @@ exports.EFFORT_CONSTANTS = {
41
42
  RECOGNIZABLE_CLONE_FROM_PRIOR_DISCOUNT: 0.33,
42
43
  RECOGNIZABLE_CLONE_FROM_OTHER_DISCOUNT: 0.33,
43
44
  REUSED_CLONE_FROM_OTHER_BONUS: 0.005,
45
+ SCAN_STEP_COST: 0.015, // Matches visual scan multiplier
46
+ SCAN_SELECTION_COST: 0.1, // Cost of a switch selection
44
47
  };
45
48
  /**
46
49
  * Calculate button size effort based on grid dimensions
@@ -196,3 +199,13 @@ function shouldSkipVisualScan(distance) {
196
199
  function localScanEffort(distance) {
197
200
  return distance * exports.EFFORT_CONSTANTS.SKIPPED_VISUAL_SCAN_DISTANCE_MULTIPLIER;
198
201
  }
202
+ /**
203
+ * Calculate effort for switch scanning
204
+ *
205
+ * @param steps - Number of scan steps to reach target
206
+ * @param selections - Number of switch selections required
207
+ * @returns Scanning effort score
208
+ */
209
+ function scanningEffort(steps, selections) {
210
+ return (steps * exports.EFFORT_CONSTANTS.SCAN_STEP_COST + selections * exports.EFFORT_CONSTANTS.SCAN_SELECTION_COST);
211
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * .obl (Open Board Logging) File Format Types
3
+ *
4
+ * Based on the .obl specification for AAC logging.
5
+ */
6
+ export interface OblAction {
7
+ action: string;
8
+ destination_board_id?: string;
9
+ text?: string;
10
+ modification_type?: string;
11
+ [key: string]: any;
12
+ }
13
+ export interface OblEventBase {
14
+ id: string;
15
+ timestamp: string;
16
+ type: 'button' | 'action' | 'utterance' | 'note' | 'other' | string;
17
+ locale?: string;
18
+ geo?: [number, number, number?];
19
+ location_id?: string;
20
+ modeling?: boolean;
21
+ system?: string;
22
+ window_width?: number;
23
+ window_height?: number;
24
+ percent_x?: number;
25
+ percent_y?: number;
26
+ [key: string]: any;
27
+ }
28
+ export interface OblButtonEvent extends OblEventBase {
29
+ type: 'button';
30
+ label: string;
31
+ spoken: boolean;
32
+ button_id?: string;
33
+ board_id?: string;
34
+ vocalization?: string;
35
+ image_url?: string;
36
+ actions?: OblAction[];
37
+ }
38
+ export interface OblActionEvent extends OblEventBase {
39
+ type: 'action';
40
+ action: string;
41
+ destination_board_id?: string;
42
+ text?: string;
43
+ modification_type?: string;
44
+ }
45
+ export interface OblUtteranceEvent extends OblEventBase {
46
+ type: 'utterance';
47
+ text: string;
48
+ buttons?: Array<{
49
+ id?: string;
50
+ label?: string;
51
+ board_id?: string;
52
+ vocalization?: string;
53
+ action?: string;
54
+ text?: string;
55
+ }>;
56
+ }
57
+ export interface OblNoteEvent extends OblEventBase {
58
+ type: 'note';
59
+ text: string;
60
+ author_name?: string;
61
+ author_email?: string;
62
+ author_url?: string;
63
+ }
64
+ export type OblEvent = OblButtonEvent | OblActionEvent | OblUtteranceEvent | OblNoteEvent | OblEventBase;
65
+ export interface OblSession {
66
+ id: string;
67
+ type: 'log' | string;
68
+ started: string;
69
+ ended: string;
70
+ device_id?: string;
71
+ locale?: string;
72
+ anonymizations?: string[];
73
+ events: OblEvent[];
74
+ [key: string]: any;
75
+ }
76
+ export interface OblFile {
77
+ format: 'open-board-log-0.1' | string;
78
+ user_id: string;
79
+ user_name?: string;
80
+ source?: string;
81
+ locale?: string;
82
+ anonymized?: boolean;
83
+ license?: {
84
+ type: string;
85
+ copyright_notice_url?: string;
86
+ source_url?: string;
87
+ author_name?: string;
88
+ author_url?: string;
89
+ author_email?: string;
90
+ };
91
+ sessions: OblSession[];
92
+ [key: string]: any;
93
+ }
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ /**
3
+ * .obl (Open Board Logging) File Format Types
4
+ *
5
+ * Based on the .obl specification for AAC logging.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,40 @@
1
+ import { OblFile } from './obl-types';
2
+ import { HistoryEntry } from '../history';
3
+ /**
4
+ * .obl (Open Board Logging) Utility
5
+ *
6
+ * Provides parsing and generation support for the .obl format.
7
+ */
8
+ export declare class OblUtil {
9
+ /**
10
+ * Parse an OBL JSON string.
11
+ * Handles the optional /* notice * / at the start of the file.
12
+ */
13
+ static parse(json: string): OblFile;
14
+ /**
15
+ * Stringify an OBL file object.
16
+ * Optionally adds the recommended notice comment.
17
+ */
18
+ static stringify(obl: OblFile, includeNotice?: boolean): string;
19
+ /**
20
+ * Convert an OBL file to internal HistoryEntry format.
21
+ */
22
+ static toHistoryEntries(obl: OblFile): HistoryEntry[];
23
+ /**
24
+ * Convert HistoryEntries to an OBL file object.
25
+ */
26
+ static fromHistoryEntries(entries: HistoryEntry[], userId: string, source?: string): OblFile;
27
+ }
28
+ /**
29
+ * .obl Anonymization Utility
30
+ */
31
+ export declare class OblAnonymizer {
32
+ /**
33
+ * Apply anonymization to an OBL file.
34
+ */
35
+ static anonymize(obl: OblFile, types: string[]): OblFile;
36
+ private static applyTimestampShift;
37
+ private static applyGeolocationMasking;
38
+ private static applyUrlStripping;
39
+ private static applyNameMasking;
40
+ }