@willwade/aac-processors 0.0.15 → 0.0.16

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.
@@ -29,6 +29,8 @@ export declare const EFFORT_CONSTANTS: {
29
29
  readonly REUSED_CLONE_FROM_OTHER_BONUS: 0.005;
30
30
  readonly SCAN_STEP_COST: 0.015;
31
31
  readonly SCAN_SELECTION_COST: 0.1;
32
+ readonly DEFAULT_SCAN_ERROR_RATE: 0.1;
33
+ readonly SCAN_RETRY_PENALTY: 1;
32
34
  };
33
35
  /**
34
36
  * Calculate button size effort based on grid dimensions
@@ -68,12 +70,13 @@ export declare function visualScanEffort(priorButtons: number): number;
68
70
  export declare function distanceEffort(x: number, y: number, entryX?: number, entryY?: number): number;
69
71
  /**
70
72
  * Calculate spelling effort for words not available in the board set
71
- * Base cost + per-letter cost
72
73
  *
73
74
  * @param word - The word to spell
75
+ * @param entryEffort - Effort to reach the spelling/keyboard page
76
+ * @param perLetterEffort - Average effort per letter on the keyboard
74
77
  * @returns Spelling effort score
75
78
  */
76
- export declare function spellingEffort(word: string): number;
79
+ export declare function spellingEffort(word: string, entryEffort?: number, perLetterEffort?: number): number;
77
80
  /**
78
81
  * Calculate base board effort
79
82
  * Combines button size and field size efforts
@@ -142,6 +145,8 @@ export declare function localScanEffort(distance: number): number;
142
145
  *
143
146
  * @param steps - Number of scan steps to reach target
144
147
  * @param selections - Number of switch selections required
148
+ * @param stepCost - Optional override for scan step cost
149
+ * @param selectionCost - Optional override for scan selection cost
145
150
  * @returns Scanning effort score
146
151
  */
147
- export declare function scanningEffort(steps: number, selections: number): number;
152
+ export declare function scanningEffort(steps: number, selections: number, stepCost?: number, selectionCost?: number): number;
@@ -44,6 +44,8 @@ exports.EFFORT_CONSTANTS = {
44
44
  REUSED_CLONE_FROM_OTHER_BONUS: 0.005,
45
45
  SCAN_STEP_COST: 0.015, // Matches visual scan multiplier
46
46
  SCAN_SELECTION_COST: 0.1, // Cost of a switch selection
47
+ DEFAULT_SCAN_ERROR_RATE: 0.1, // 10% chance of missing a selection
48
+ SCAN_RETRY_PENALTY: 1.0, // Cost multiplier for a full loop retry
47
49
  };
48
50
  /**
49
51
  * Calculate button size effort based on grid dimensions
@@ -92,13 +94,14 @@ function distanceEffort(x, y, entryX = 1.0, entryY = 1.0) {
92
94
  }
93
95
  /**
94
96
  * Calculate spelling effort for words not available in the board set
95
- * Base cost + per-letter cost
96
97
  *
97
98
  * @param word - The word to spell
99
+ * @param entryEffort - Effort to reach the spelling/keyboard page
100
+ * @param perLetterEffort - Average effort per letter on the keyboard
98
101
  * @returns Spelling effort score
99
102
  */
100
- function spellingEffort(word) {
101
- return 10 + word.length * 2.5;
103
+ function spellingEffort(word, entryEffort = 10, perLetterEffort = 2.5) {
104
+ return entryEffort + word.length * perLetterEffort;
102
105
  }
103
106
  /**
104
107
  * Calculate base board effort
@@ -204,8 +207,10 @@ function localScanEffort(distance) {
204
207
  *
205
208
  * @param steps - Number of scan steps to reach target
206
209
  * @param selections - Number of switch selections required
210
+ * @param stepCost - Optional override for scan step cost
211
+ * @param selectionCost - Optional override for scan selection cost
207
212
  * @returns Scanning effort score
208
213
  */
209
- function scanningEffort(steps, selections) {
210
- return (steps * exports.EFFORT_CONSTANTS.SCAN_STEP_COST + selections * exports.EFFORT_CONSTANTS.SCAN_SELECTION_COST);
214
+ function scanningEffort(steps, selections, stepCost = exports.EFFORT_CONSTANTS.SCAN_STEP_COST, selectionCost = exports.EFFORT_CONSTANTS.SCAN_SELECTION_COST) {
215
+ return steps * stepCost + selections * selectionCost;
211
216
  }
@@ -40,10 +40,23 @@ class SentenceAnalyzer {
40
40
  totalEffort += found.effort;
41
41
  }
42
42
  else {
43
- // Word not found - use spelling effort
44
- const spellEffort = (0, effort_1.spellingEffort)(word);
45
- wordEfforts.push({ word, effort: spellEffort, typed: true });
46
- totalEffort += spellEffort;
43
+ // Word not found - check for dynamic prediction fallback
44
+ let wordEffort = 0;
45
+ const isTyped = true;
46
+ const baseSpell = (0, effort_1.spellingEffort)(word, metrics.spelling_effort_base || 10, metrics.spelling_effort_per_letter || 2.5);
47
+ if (metrics.has_dynamic_prediction) {
48
+ // Predictive fallback: Base + (limited letters) + selection
49
+ // We assume on average typing 40% of the word finds it in the dictionary
50
+ const predictiveEffort = (metrics.spelling_effort_base || 10) +
51
+ word.length * 0.4 * (metrics.spelling_effort_per_letter || 2.5) +
52
+ 6.0; // Fixed selection cost from prediction bar
53
+ wordEffort = Math.min(baseSpell, predictiveEffort);
54
+ }
55
+ else {
56
+ wordEffort = baseSpell;
57
+ }
58
+ wordEfforts.push({ word, effort: wordEffort, typed: isTyped });
59
+ totalEffort += wordEffort;
47
60
  typing = true;
48
61
  missingWords.push(word);
49
62
  }
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Defines the data structures used for AAC metrics analysis
5
5
  */
6
+ import { ScanningConfig } from '../../../types/aac';
6
7
  /**
7
8
  * Button-level metrics result
8
9
  */
@@ -52,6 +53,11 @@ export interface MetricsResult {
52
53
  alternates?: {
53
54
  [boardId: string]: AlternateBoardMetrics;
54
55
  };
56
+ spelling_effort_base?: number;
57
+ spelling_effort_per_letter?: number;
58
+ spelling_page_id?: string;
59
+ has_dynamic_prediction?: boolean;
60
+ prediction_page_id?: string;
55
61
  obfset?: any;
56
62
  }
57
63
  /**
@@ -63,6 +69,32 @@ export interface AlternateBoardMetrics {
63
69
  [level: number]: ButtonMetrics[];
64
70
  };
65
71
  }
72
+ /**
73
+ * Options for metrics calculation
74
+ */
75
+ export interface MetricsOptions {
76
+ /**
77
+ * Override scanning configuration
78
+ */
79
+ scanningConfig?: ScanningConfig;
80
+ /**
81
+ * Path to core vocabulary lists to use for analysis
82
+ */
83
+ coreLists?: string[];
84
+ /**
85
+ * Test sentences for sentence-level effort analysis
86
+ */
87
+ testSentences?: string[];
88
+ /**
89
+ * Custom scanning costs
90
+ */
91
+ scanStepCost?: number;
92
+ scanSelectionCost?: number;
93
+ /**
94
+ * Optional explicit ID of the spelling/keyboard page
95
+ */
96
+ spellingPageId?: string;
97
+ }
66
98
  /**
67
99
  * Comparison result between two board sets
68
100
  */
@@ -76,6 +108,13 @@ export interface ComparisonResult extends MetricsResult {
76
108
  columns: number;
77
109
  };
78
110
  comp_effort_score: number;
111
+ comp_spelling_effort_base?: number;
112
+ comp_spelling_effort_per_letter?: number;
113
+ comp_spelling_page_id?: string;
114
+ has_dynamic_prediction?: boolean;
115
+ prediction_page_id?: string;
116
+ comp_has_dynamic_prediction?: boolean;
117
+ comp_prediction_page_id?: string;
79
118
  missing_words: string[];
80
119
  extra_words: string[];
81
120
  overlapping_words: string[];
@@ -130,7 +130,7 @@ class VocabularyAnalyzer {
130
130
  if (btn) {
131
131
  return btn.effort;
132
132
  }
133
- return (0, effort_1.spellingEffort)(word);
133
+ return (0, effort_1.spellingEffort)(word, metrics.spelling_effort_base, metrics.spelling_effort_per_letter);
134
134
  }
135
135
  /**
136
136
  * Check if a word is in the board set
@@ -75,7 +75,18 @@ class ReferenceLoader {
75
75
  loadFringe() {
76
76
  const filePath = path.join(this.dataDir, `fringe.${this.locale}.json`);
77
77
  const content = fs.readFileSync(filePath, 'utf-8');
78
- return JSON.parse(content);
78
+ const data = JSON.parse(content);
79
+ // Flatten nested category words if needed
80
+ if (Array.isArray(data) && data.length > 0 && data[0].categories) {
81
+ const flattened = [];
82
+ data.forEach((list) => {
83
+ list.categories.forEach((cat) => {
84
+ flattened.push(...cat.words);
85
+ });
86
+ });
87
+ return flattened;
88
+ }
89
+ return data;
79
90
  }
80
91
  /**
81
92
  * Load base words hash map
@@ -39,6 +39,7 @@ export interface ButtonForTranslation {
39
39
  message: string;
40
40
  textToTranslate: string;
41
41
  symbols: SymbolInfo[];
42
+ grammar?: any;
42
43
  }
43
44
  /**
44
45
  * LLM translation result with symbol mappings
@@ -68,7 +69,7 @@ export interface LLMLTranslationResult {
68
69
  export declare function normalizeButtonForTranslation(buttonId: string, label: string, message: string, symbols: SymbolInfo[], context?: {
69
70
  pageId?: string;
70
71
  pageName?: string;
71
- }): ButtonForTranslation;
72
+ }, grammar?: any): ButtonForTranslation;
72
73
  /**
73
74
  * Extract symbols from various button formats.
74
75
  *
@@ -39,13 +39,14 @@ exports.validateTranslationResults = validateTranslationResults;
39
39
  * @param context - Optional page context
40
40
  * @returns Normalized button data for translation
41
41
  */
42
- function normalizeButtonForTranslation(buttonId, label, message, symbols, context) {
42
+ function normalizeButtonForTranslation(buttonId, label, message, symbols, context, grammar) {
43
43
  return {
44
44
  buttonId,
45
45
  label,
46
46
  message,
47
47
  textToTranslate: message || label, // Translate message if present, otherwise label
48
48
  symbols,
49
+ grammar,
49
50
  ...context,
50
51
  };
51
52
  }
@@ -119,7 +120,8 @@ function extractAllButtonsForTranslation(buttons, contextFn) {
119
120
  if (!label && !message)
120
121
  continue;
121
122
  const context = contextFn ? contextFn(button) : undefined;
122
- results.push(normalizeButtonForTranslation(buttonId, label, message, symbols || [], context));
123
+ const grammar = button.parameters?.grammar || undefined;
124
+ results.push(normalizeButtonForTranslation(buttonId, label, message, symbols || [], context, grammar));
123
125
  }
124
126
  return results;
125
127
  }
@@ -144,6 +146,7 @@ Each button has:
144
146
  - message: The text spoken when the button is activated
145
147
  - textToTranslate: The actual text to translate (usually the message)
146
148
  - symbols: Visual symbols attached to specific words
149
+ - grammar: Grammatical context (e.g., pos: Part of Speech, person, number)
147
150
 
148
151
  IMPORTANT: After translation, you MUST reattach symbols to the correct translated words based on MEANING, not position.
149
152
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@willwade/aac-processors",
3
- "version": "0.0.15",
3
+ "version": "0.0.16",
4
4
  "description": "A comprehensive TypeScript library for processing AAC (Augmentative and Alternative Communication) file formats with translation support",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",