@willwade/aac-processors 0.0.12 → 0.0.13
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/README.md +44 -41
- package/dist/core/treeStructure.d.ts +35 -2
- package/dist/core/treeStructure.js +18 -3
- package/dist/optional/analytics/history.d.ts +12 -1
- package/dist/optional/analytics/index.d.ts +2 -0
- package/dist/optional/analytics/index.js +6 -1
- package/dist/optional/analytics/metrics/comparison.js +8 -4
- package/dist/optional/analytics/metrics/core.d.ts +9 -0
- package/dist/optional/analytics/metrics/core.js +190 -37
- package/dist/optional/analytics/metrics/effort.d.ts +10 -0
- package/dist/optional/analytics/metrics/effort.js +13 -0
- package/dist/optional/analytics/metrics/obl-types.d.ts +93 -0
- package/dist/optional/analytics/metrics/obl-types.js +7 -0
- package/dist/optional/analytics/metrics/obl.d.ts +40 -0
- package/dist/optional/analytics/metrics/obl.js +287 -0
- package/dist/optional/analytics/metrics/vocabulary.js +6 -4
- package/dist/optional/symbolTools.js +13 -16
- package/dist/processors/astericsGridProcessor.d.ts +15 -0
- package/dist/processors/astericsGridProcessor.js +17 -0
- package/dist/processors/gridset/helpers.d.ts +4 -1
- package/dist/processors/gridset/helpers.js +4 -0
- package/dist/processors/gridset/pluginTypes.js +51 -50
- package/dist/processors/gridset/symbolExtractor.js +3 -2
- package/dist/processors/gridset/symbolSearch.js +9 -7
- package/dist/processors/gridsetProcessor.js +57 -20
- package/dist/processors/obfProcessor.js +12 -0
- package/dist/processors/snap/helpers.d.ts +5 -1
- package/dist/processors/snap/helpers.js +5 -0
- package/dist/processors/snapProcessor.d.ts +2 -0
- package/dist/processors/snapProcessor.js +155 -4
- package/dist/processors/touchchatProcessor.js +24 -5
- package/dist/types/aac.d.ts +63 -0
- package/dist/types/aac.js +33 -0
- package/dist/validation/gridsetValidator.js +10 -0
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
302
|
+
sEffort = Math.min(sEffort, sEffort * discount);
|
|
250
303
|
}
|
|
251
|
-
else if (boardPcts[
|
|
252
|
-
const discount = effort_1.EFFORT_CONSTANTS.
|
|
253
|
-
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
boardPcts[
|
|
270
|
-
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
|
|
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,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
|
+
}
|