@willwade/aac-processors 0.0.11 → 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.
Files changed (56) hide show
  1. package/README.md +44 -41
  2. package/dist/cli/index.js +7 -0
  3. package/dist/core/analyze.js +1 -0
  4. package/dist/core/treeStructure.d.ts +45 -2
  5. package/dist/core/treeStructure.js +22 -3
  6. package/dist/index.d.ts +2 -1
  7. package/dist/index.js +20 -3
  8. package/dist/{analytics → optional/analytics}/history.d.ts +15 -4
  9. package/dist/{analytics → optional/analytics}/history.js +3 -3
  10. package/dist/optional/analytics/index.d.ts +30 -0
  11. package/dist/optional/analytics/index.js +78 -0
  12. package/dist/optional/analytics/metrics/comparison.d.ts +36 -0
  13. package/dist/optional/analytics/metrics/comparison.js +334 -0
  14. package/dist/optional/analytics/metrics/core.d.ts +45 -0
  15. package/dist/optional/analytics/metrics/core.js +575 -0
  16. package/dist/optional/analytics/metrics/effort.d.ts +147 -0
  17. package/dist/optional/analytics/metrics/effort.js +211 -0
  18. package/dist/optional/analytics/metrics/index.d.ts +15 -0
  19. package/dist/optional/analytics/metrics/index.js +36 -0
  20. package/dist/optional/analytics/metrics/obl-types.d.ts +93 -0
  21. package/dist/optional/analytics/metrics/obl-types.js +7 -0
  22. package/dist/optional/analytics/metrics/obl.d.ts +40 -0
  23. package/dist/optional/analytics/metrics/obl.js +287 -0
  24. package/dist/optional/analytics/metrics/sentence.d.ts +49 -0
  25. package/dist/optional/analytics/metrics/sentence.js +112 -0
  26. package/dist/optional/analytics/metrics/types.d.ts +157 -0
  27. package/dist/optional/analytics/metrics/types.js +7 -0
  28. package/dist/optional/analytics/metrics/vocabulary.d.ts +65 -0
  29. package/dist/optional/analytics/metrics/vocabulary.js +142 -0
  30. package/dist/optional/analytics/reference/index.d.ts +51 -0
  31. package/dist/optional/analytics/reference/index.js +102 -0
  32. package/dist/optional/analytics/utils/idGenerator.d.ts +59 -0
  33. package/dist/optional/analytics/utils/idGenerator.js +96 -0
  34. package/dist/optional/symbolTools.js +13 -16
  35. package/dist/processors/astericsGridProcessor.d.ts +15 -0
  36. package/dist/processors/astericsGridProcessor.js +17 -0
  37. package/dist/processors/gridset/helpers.d.ts +4 -1
  38. package/dist/processors/gridset/helpers.js +4 -0
  39. package/dist/processors/gridset/pluginTypes.js +51 -50
  40. package/dist/processors/gridset/symbolExtractor.js +3 -2
  41. package/dist/processors/gridset/symbolSearch.js +9 -7
  42. package/dist/processors/gridsetProcessor.js +82 -20
  43. package/dist/processors/index.d.ts +1 -0
  44. package/dist/processors/index.js +5 -3
  45. package/dist/processors/obfProcessor.js +37 -2
  46. package/dist/processors/obfsetProcessor.d.ts +26 -0
  47. package/dist/processors/obfsetProcessor.js +179 -0
  48. package/dist/processors/snap/helpers.d.ts +5 -1
  49. package/dist/processors/snap/helpers.js +5 -0
  50. package/dist/processors/snapProcessor.d.ts +2 -0
  51. package/dist/processors/snapProcessor.js +184 -5
  52. package/dist/processors/touchchatProcessor.js +50 -4
  53. package/dist/types/aac.d.ts +67 -0
  54. package/dist/types/aac.js +33 -0
  55. package/dist/validation/gridsetValidator.js +10 -0
  56. package/package.json +1 -1
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ /**
3
+ * Vocabulary Coverage Analysis
4
+ *
5
+ * Analyzes how well an AAC board set covers core vocabulary
6
+ * and identifies missing/extra words compared to reference lists.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.VocabularyAnalyzer = void 0;
10
+ const index_1 = require("../reference/index");
11
+ const effort_1 = require("./effort");
12
+ class VocabularyAnalyzer {
13
+ constructor(referenceLoader) {
14
+ this.referenceLoader = referenceLoader || new index_1.ReferenceLoader();
15
+ }
16
+ /**
17
+ * Analyze vocabulary coverage against core lists
18
+ */
19
+ analyze(metrics, options) {
20
+ // const locale = options?.locale || metrics.locale || 'en';
21
+ const highEffortThreshold = options?.highEffortThreshold || 5.0;
22
+ const lowEffortThreshold = options?.lowEffortThreshold || 2.0;
23
+ // Load reference data
24
+ const coreLists = this.referenceLoader.loadCoreLists();
25
+ // Create word to effort map (using lowercase keys for matching)
26
+ const wordEffortMap = new Map();
27
+ metrics.buttons.forEach((btn) => {
28
+ const word = btn.label.toLowerCase();
29
+ const existing = wordEffortMap.get(word);
30
+ if (!existing || btn.effort < existing) {
31
+ wordEffortMap.set(word, btn.effort);
32
+ }
33
+ });
34
+ // Analyze each core list
35
+ const core_coverage = {};
36
+ coreLists.forEach((list) => {
37
+ const analysis = this.analyzeCoreList(list, wordEffortMap);
38
+ core_coverage[list.id] = analysis;
39
+ });
40
+ // Find extra words (words not in any core list)
41
+ const allCoreWords = new Set();
42
+ coreLists.forEach((list) => {
43
+ list.words.forEach((word) => allCoreWords.add(word.toLowerCase()));
44
+ });
45
+ const extraWords = [];
46
+ wordEffortMap.forEach((effort, word) => {
47
+ if (!allCoreWords.has(word.toLowerCase())) {
48
+ extraWords.push(word);
49
+ }
50
+ });
51
+ extraWords.sort((a, b) => a.localeCompare(b));
52
+ // Find high/low effort words
53
+ const highEffortWords = [];
54
+ const lowEffortWords = [];
55
+ wordEffortMap.forEach((effort, word) => {
56
+ if (effort > highEffortThreshold) {
57
+ highEffortWords.push({ word, effort });
58
+ }
59
+ else if (effort < lowEffortThreshold) {
60
+ lowEffortWords.push({ word, effort });
61
+ }
62
+ });
63
+ highEffortWords.sort((a, b) => b.effort - a.effort);
64
+ lowEffortWords.sort((a, b) => a.effort - b.effort);
65
+ return {
66
+ core_coverage,
67
+ total_unique_words: wordEffortMap.size,
68
+ words_with_effort: wordEffortMap.size,
69
+ words_requiring_spelling: 0, // Calculated during sentence analysis
70
+ extra_words: extraWords,
71
+ high_effort_words: highEffortWords.slice(0, 50), // Top 50
72
+ low_effort_words: lowEffortWords.slice(0, 50), // Bottom 50
73
+ };
74
+ }
75
+ /**
76
+ * Analyze coverage for a single core list
77
+ */
78
+ analyzeCoreList(list, wordEffortMap) {
79
+ const covered = [];
80
+ const missing = [];
81
+ let totalEffort = 0;
82
+ list.words.forEach((word) => {
83
+ const lowerWord = word.toLowerCase();
84
+ const effort = wordEffortMap.get(lowerWord);
85
+ if (effort !== undefined) {
86
+ covered.push(word);
87
+ totalEffort += effort;
88
+ }
89
+ else {
90
+ missing.push(word);
91
+ }
92
+ });
93
+ const averageEffort = covered.length > 0 ? totalEffort / covered.length : 0;
94
+ return {
95
+ name: list.name,
96
+ total_words: list.words.length,
97
+ covered: covered.length,
98
+ missing: missing.length,
99
+ coverage_percent: (covered.length / list.words.length) * 100,
100
+ missing_words: missing,
101
+ average_effort: averageEffort,
102
+ };
103
+ }
104
+ /**
105
+ * Calculate coverage percentage for a specific word list
106
+ */
107
+ calculateCoverage(wordList, metrics) {
108
+ const wordSet = new Set(metrics.buttons.map((btn) => btn.label.toLowerCase()));
109
+ const covered = [];
110
+ const missing = [];
111
+ wordList.forEach((word) => {
112
+ if (wordSet.has(word.toLowerCase())) {
113
+ covered.push(word);
114
+ }
115
+ else {
116
+ missing.push(word);
117
+ }
118
+ });
119
+ return {
120
+ covered,
121
+ missing,
122
+ coverage_percent: (covered.length / wordList.length) * 100,
123
+ };
124
+ }
125
+ /**
126
+ * Get effort for a word, or calculate spelling effort if missing
127
+ */
128
+ getWordEffort(word, metrics) {
129
+ const btn = metrics.buttons.find((b) => b.label.toLowerCase() === word.toLowerCase());
130
+ if (btn) {
131
+ return btn.effort;
132
+ }
133
+ return (0, effort_1.spellingEffort)(word);
134
+ }
135
+ /**
136
+ * Check if a word is in the board set
137
+ */
138
+ hasWord(word, metrics) {
139
+ return metrics.buttons.some((b) => b.label.toLowerCase() === word.toLowerCase());
140
+ }
141
+ }
142
+ exports.VocabularyAnalyzer = VocabularyAnalyzer;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Reference Data Loader
3
+ *
4
+ * Loads reference vocabulary lists, core lists, and sentences
5
+ * for AAC metrics analysis.
6
+ */
7
+ import { CoreList, CommonWordsData, SynonymsData } from '../metrics/types';
8
+ export declare class ReferenceLoader {
9
+ private dataDir;
10
+ private locale;
11
+ constructor(dataDir?: string, locale?: string);
12
+ /**
13
+ * Load core vocabulary lists
14
+ */
15
+ loadCoreLists(): CoreList[];
16
+ /**
17
+ * Load common words with baseline effort scores
18
+ */
19
+ loadCommonWords(): CommonWordsData;
20
+ /**
21
+ * Load synonym mappings
22
+ */
23
+ loadSynonyms(): SynonymsData;
24
+ /**
25
+ * Load test sentences
26
+ */
27
+ loadSentences(): string[][];
28
+ /**
29
+ * Load fringe vocabulary
30
+ */
31
+ loadFringe(): string[];
32
+ /**
33
+ * Load base words hash map
34
+ */
35
+ loadBaseWords(): {
36
+ [word: string]: boolean;
37
+ };
38
+ /**
39
+ * Get all reference data at once
40
+ */
41
+ loadAll(): {
42
+ coreLists: CoreList[];
43
+ commonWords: CommonWordsData;
44
+ synonyms: SynonymsData;
45
+ sentences: string[][];
46
+ fringe: string[];
47
+ baseWords: {
48
+ [word: string]: boolean;
49
+ };
50
+ };
51
+ }
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ /**
3
+ * Reference Data Loader
4
+ *
5
+ * Loads reference vocabulary lists, core lists, and sentences
6
+ * for AAC metrics analysis.
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || function (mod) {
25
+ if (mod && mod.__esModule) return mod;
26
+ var result = {};
27
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
28
+ __setModuleDefault(result, mod);
29
+ return result;
30
+ };
31
+ Object.defineProperty(exports, "__esModule", { value: true });
32
+ exports.ReferenceLoader = void 0;
33
+ const fs = __importStar(require("fs"));
34
+ const path = __importStar(require("path"));
35
+ class ReferenceLoader {
36
+ constructor(dataDir = path.join(__dirname, 'data'), locale = 'en') {
37
+ this.dataDir = dataDir;
38
+ this.locale = locale;
39
+ }
40
+ /**
41
+ * Load core vocabulary lists
42
+ */
43
+ loadCoreLists() {
44
+ const filePath = path.join(this.dataDir, `core_lists.${this.locale}.json`);
45
+ const content = fs.readFileSync(filePath, 'utf-8');
46
+ return JSON.parse(content);
47
+ }
48
+ /**
49
+ * Load common words with baseline effort scores
50
+ */
51
+ loadCommonWords() {
52
+ const filePath = path.join(this.dataDir, `common_words.${this.locale}.json`);
53
+ const content = fs.readFileSync(filePath, 'utf-8');
54
+ return JSON.parse(content);
55
+ }
56
+ /**
57
+ * Load synonym mappings
58
+ */
59
+ loadSynonyms() {
60
+ const filePath = path.join(this.dataDir, `synonyms.${this.locale}.json`);
61
+ const content = fs.readFileSync(filePath, 'utf-8');
62
+ return JSON.parse(content);
63
+ }
64
+ /**
65
+ * Load test sentences
66
+ */
67
+ loadSentences() {
68
+ const filePath = path.join(this.dataDir, `sentences.${this.locale}.json`);
69
+ const content = fs.readFileSync(filePath, 'utf-8');
70
+ return JSON.parse(content);
71
+ }
72
+ /**
73
+ * Load fringe vocabulary
74
+ */
75
+ loadFringe() {
76
+ const filePath = path.join(this.dataDir, `fringe.${this.locale}.json`);
77
+ const content = fs.readFileSync(filePath, 'utf-8');
78
+ return JSON.parse(content);
79
+ }
80
+ /**
81
+ * Load base words hash map
82
+ */
83
+ loadBaseWords() {
84
+ const filePath = path.join(this.dataDir, `base_words.${this.locale}.json`);
85
+ const content = fs.readFileSync(filePath, 'utf-8');
86
+ return JSON.parse(content);
87
+ }
88
+ /**
89
+ * Get all reference data at once
90
+ */
91
+ loadAll() {
92
+ return {
93
+ coreLists: this.loadCoreLists(),
94
+ commonWords: this.loadCommonWords(),
95
+ synonyms: this.loadSynonyms(),
96
+ sentences: this.loadSentences(),
97
+ fringe: this.loadFringe(),
98
+ baseWords: this.loadBaseWords(),
99
+ };
100
+ }
101
+ }
102
+ exports.ReferenceLoader = ReferenceLoader;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * ID Generator Utility for AAC Metrics
3
+ *
4
+ * Generates clone_id values based on grid location and button label.
5
+ * Clone IDs help identify buttons that appear in the same location
6
+ * across different boards in an AAC system.
7
+ */
8
+ /**
9
+ * Normalize a label for use in clone_id generation
10
+ * Converts to lowercase, removes apostrophes, trims whitespace
11
+ *
12
+ * @param label - The button label to normalize
13
+ * @returns Normalized label string
14
+ */
15
+ export declare function normalizeLabelForCloneId(label: string): string;
16
+ /**
17
+ * Generate a clone_id based on grid location and button label
18
+ *
19
+ * Clone ID format: "{rows}x{cols}-{row}.{col}-{label_normalized}"
20
+ * Example: "6x4-2.3-more" for button "more" at row 2, col 3 in a 6x4 grid
21
+ *
22
+ * @param rows - Total number of rows in the grid
23
+ * @param cols - Total number of columns in the grid
24
+ * @param row - Zero-based row index of the button
25
+ * @param col - Zero-based column index of the button
26
+ * @param label - The button label
27
+ * @returns A clone_id string
28
+ */
29
+ export declare function generateCloneId(rows: number, cols: number, row: number, col: number, label: string): string;
30
+ /**
31
+ * Generate a semantic_id based on button content
32
+ *
33
+ * Semantic IDs identify buttons with the same semantic meaning across boards.
34
+ * This is a fallback for formats that don't have explicit semantic IDs.
35
+ * Based on hash of message + label
36
+ *
37
+ * @param message - The button message/vocalization
38
+ * @param label - The button label
39
+ * @returns A semantic_id string (hash-based)
40
+ */
41
+ export declare function generateSemanticId(message: string, label: string): string;
42
+ /**
43
+ * Extract all semantic_ids from a page's buttons
44
+ *
45
+ * @param buttons - Array of buttons to scan
46
+ * @returns Array of unique semantic_id strings
47
+ */
48
+ export declare function extractSemanticIds(buttons: Array<{
49
+ semantic_id?: string;
50
+ }>): string[];
51
+ /**
52
+ * Extract all clone_ids from a page's buttons
53
+ *
54
+ * @param buttons - Array of buttons to scan
55
+ * @returns Array of unique clone_id strings
56
+ */
57
+ export declare function extractCloneIds(buttons: Array<{
58
+ clone_id?: string;
59
+ }>): string[];
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ /**
3
+ * ID Generator Utility for AAC Metrics
4
+ *
5
+ * Generates clone_id values based on grid location and button label.
6
+ * Clone IDs help identify buttons that appear in the same location
7
+ * across different boards in an AAC system.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.normalizeLabelForCloneId = normalizeLabelForCloneId;
11
+ exports.generateCloneId = generateCloneId;
12
+ exports.generateSemanticId = generateSemanticId;
13
+ exports.extractSemanticIds = extractSemanticIds;
14
+ exports.extractCloneIds = extractCloneIds;
15
+ /**
16
+ * Normalize a label for use in clone_id generation
17
+ * Converts to lowercase, removes apostrophes, trims whitespace
18
+ *
19
+ * @param label - The button label to normalize
20
+ * @returns Normalized label string
21
+ */
22
+ function normalizeLabelForCloneId(label) {
23
+ return label
24
+ .toLowerCase()
25
+ .replace(/['']/g, '') // Remove apostrophes
26
+ .replace(/\s+/g, '_') // Replace spaces with underscores
27
+ .trim();
28
+ }
29
+ /**
30
+ * Generate a clone_id based on grid location and button label
31
+ *
32
+ * Clone ID format: "{rows}x{cols}-{row}.{col}-{label_normalized}"
33
+ * Example: "6x4-2.3-more" for button "more" at row 2, col 3 in a 6x4 grid
34
+ *
35
+ * @param rows - Total number of rows in the grid
36
+ * @param cols - Total number of columns in the grid
37
+ * @param row - Zero-based row index of the button
38
+ * @param col - Zero-based column index of the button
39
+ * @param label - The button label
40
+ * @returns A clone_id string
41
+ */
42
+ function generateCloneId(rows, cols, row, col, label) {
43
+ const normalizedLabel = normalizeLabelForCloneId(label);
44
+ return `${rows}x${cols}-${row}.${col}-${normalizedLabel}`;
45
+ }
46
+ /**
47
+ * Generate a semantic_id based on button content
48
+ *
49
+ * Semantic IDs identify buttons with the same semantic meaning across boards.
50
+ * This is a fallback for formats that don't have explicit semantic IDs.
51
+ * Based on hash of message + label
52
+ *
53
+ * @param message - The button message/vocalization
54
+ * @param label - The button label
55
+ * @returns A semantic_id string (hash-based)
56
+ */
57
+ function generateSemanticId(message, label) {
58
+ const content = `${message || ''}::${label || ''}`;
59
+ // Simple hash function (djb2 algorithm)
60
+ let hash = 5381;
61
+ for (let i = 0; i < content.length; i++) {
62
+ hash = (hash * 33) ^ content.charCodeAt(i);
63
+ }
64
+ // Convert to positive hex string
65
+ return `semantic_${(hash >>> 0).toString(16)}`;
66
+ }
67
+ /**
68
+ * Extract all semantic_ids from a page's buttons
69
+ *
70
+ * @param buttons - Array of buttons to scan
71
+ * @returns Array of unique semantic_id strings
72
+ */
73
+ function extractSemanticIds(buttons) {
74
+ const ids = new Set();
75
+ for (const button of buttons) {
76
+ if (button.semantic_id) {
77
+ ids.add(button.semantic_id);
78
+ }
79
+ }
80
+ return Array.from(ids);
81
+ }
82
+ /**
83
+ * Extract all clone_ids from a page's buttons
84
+ *
85
+ * @param buttons - Array of buttons to scan
86
+ * @returns Array of unique clone_id strings
87
+ */
88
+ function extractCloneIds(buttons) {
89
+ const ids = new Set();
90
+ for (const button of buttons) {
91
+ if (button.clone_id) {
92
+ ids.add(button.clone_id);
93
+ }
94
+ }
95
+ return Array.from(ids);
96
+ }
@@ -7,7 +7,7 @@ exports.TouchChatSymbolResolver = exports.TouchChatSymbolExtractor = exports.Gri
7
7
  exports.resolveSymbol = resolveSymbol;
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const fs_1 = __importDefault(require("fs"));
10
- const password_1 = require("../processors/gridset/password");
10
+ const symbols_1 = require("../processors/gridset/symbols");
11
11
  // --- Base Classes ---
12
12
  class SymbolExtractor {
13
13
  }
@@ -61,10 +61,13 @@ exports.SnapSymbolResolver = SnapSymbolResolver;
61
61
  let AdmZip = null;
62
62
  let XMLParser = null;
63
63
  try {
64
+ // Dynamic requires for optional dependencies
64
65
  // eslint-disable-next-line @typescript-eslint/no-var-requires
65
- AdmZip = require('adm-zip');
66
+ const admZipModule = require('adm-zip');
66
67
  // eslint-disable-next-line @typescript-eslint/no-var-requires
67
- XMLParser = require('fast-xml-parser').XMLParser;
68
+ const fxpModule = require('fast-xml-parser');
69
+ AdmZip = admZipModule;
70
+ XMLParser = fxpModule.XMLParser;
68
71
  }
69
72
  catch {
70
73
  AdmZip = null;
@@ -74,19 +77,13 @@ class Grid3SymbolExtractor extends SymbolExtractor {
74
77
  getSymbolReferences(filePath) {
75
78
  if (!AdmZip || !XMLParser)
76
79
  throw new Error('adm-zip or fast-xml-parser not installed');
77
- const zip = new AdmZip(filePath);
78
- const parser = new XMLParser();
79
- const refs = new Set();
80
- const entries = (0, password_1.getZipEntriesWithPassword)(zip, (0, password_1.resolveGridsetPasswordFromEnv)());
81
- entries.forEach((entry) => {
82
- if (entry.entryName.endsWith('.gridset') || entry.entryName.endsWith('.gridsetx')) {
83
- const xmlBuffer = entry.getData();
84
- // Parse to validate XML structure (future: extract refs)
85
- parser.parse(xmlBuffer.toString('utf8'));
86
- // TODO: Extract symbol references from Grid 3 XML structure when needed
87
- }
88
- });
89
- return Array.from(refs);
80
+ // Import GridsetProcessor dynamically to avoid circular dependencies
81
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
82
+ const { GridsetProcessor } = require('../processors/gridsetProcessor');
83
+ const proc = new GridsetProcessor();
84
+ const tree = proc.loadIntoTree(filePath);
85
+ // Use the existing extractSymbolReferences function from gridset/symbols.ts
86
+ return (0, symbols_1.extractSymbolReferences)(tree);
90
87
  }
91
88
  }
92
89
  exports.Grid3SymbolExtractor = Grid3SymbolExtractor;
@@ -1,5 +1,20 @@
1
1
  import { BaseProcessor, ProcessorOptions, ExtractStringsResult, TranslatedString, SourceString } from '../core/baseProcessor';
2
2
  import { AACTree } from '../core/treeStructure';
3
+ export declare function normalizeHexColor(hexColor: string): string | null;
4
+ export declare function adjustHexColor(hexColor: string, amount: number): string;
5
+ export declare function getHighContrastNeutralColor(backgroundColor: string): string;
6
+ /**
7
+ * Calculate relative luminance of a color using WCAG formula
8
+ * @param hexColor - Hex color string (e.g., "#1d90ff")
9
+ * @returns Relative luminance value between 0 and 1
10
+ */
11
+ export declare function calculateLuminance(hexColor: string): number;
12
+ /**
13
+ * Choose white or black text color based on background luminance for optimal contrast
14
+ * @param backgroundColor - Background color hex string
15
+ * @returns "#FFFFFF" for dark backgrounds, "#000000" for light backgrounds
16
+ */
17
+ export declare function getContrastingTextColor(backgroundColor: string): string;
3
18
  declare class AstericsGridProcessor extends BaseProcessor {
4
19
  private loadAudio;
5
20
  constructor(options?: ProcessorOptions & {
@@ -4,6 +4,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.AstericsGridProcessor = void 0;
7
+ exports.normalizeHexColor = normalizeHexColor;
8
+ exports.adjustHexColor = adjustHexColor;
9
+ exports.getHighContrastNeutralColor = getHighContrastNeutralColor;
10
+ exports.calculateLuminance = calculateLuminance;
11
+ exports.getContrastingTextColor = getContrastingTextColor;
7
12
  const baseProcessor_1 = require("../core/baseProcessor");
8
13
  const treeStructure_1 = require("../core/treeStructure");
9
14
  const fs_1 = __importDefault(require("fs"));
@@ -535,6 +540,17 @@ function getContrastingTextColor(backgroundColor) {
535
540
  // WCAG threshold: use white text if luminance < 0.5, black otherwise
536
541
  return luminance < 0.5 ? '#FFFFFF' : '#000000';
537
542
  }
543
+ /**
544
+ * Map Asterics Grid hidden value to AAC standard visibility
545
+ * Asterics Grid: true = hidden, false = visible
546
+ * Maps to: 'Hidden' | 'Visible' | undefined
547
+ */
548
+ function mapAstericsVisibility(hidden) {
549
+ if (hidden === undefined) {
550
+ return undefined; // Default to visible
551
+ }
552
+ return hidden ? 'Hidden' : 'Visible';
553
+ }
538
554
  class AstericsGridProcessor extends baseProcessor_1.BaseProcessor {
539
555
  constructor(options = {}) {
540
556
  super(options);
@@ -979,6 +995,7 @@ class AstericsGridProcessor extends baseProcessor_1.BaseProcessor {
979
995
  targetPageId: targetPageId || undefined,
980
996
  semanticAction: semanticAction,
981
997
  audioRecording: audioRecording,
998
+ visibility: mapAstericsVisibility(element.hidden),
982
999
  image: imageName, // Store image filename/reference
983
1000
  parameters: imageData
984
1001
  ? {
@@ -1,4 +1,4 @@
1
- import { AACTree } from '../../core/treeStructure';
1
+ import { AACTree, AACSemanticCategory, AACSemanticIntent } from '../../core/treeStructure';
2
2
  /**
3
3
  * Build a map of button IDs to resolved image entry paths for a specific page.
4
4
  * Helpful when rewriting zip entry names or validating images referenced in a grid.
@@ -66,6 +66,9 @@ export interface Grid3HistoryEntry {
66
66
  timestamp: Date;
67
67
  latitude?: number | null;
68
68
  longitude?: number | null;
69
+ type?: 'button' | 'action' | 'utterance' | 'note' | 'other';
70
+ intent?: AACSemanticIntent | string;
71
+ category?: AACSemanticCategory;
69
72
  }>;
70
73
  rawXml?: string;
71
74
  }
@@ -44,6 +44,7 @@ exports.readGrid3HistoryForUser = readGrid3HistoryForUser;
44
44
  exports.readAllGrid3History = readAllGrid3History;
45
45
  const adm_zip_1 = __importDefault(require("adm-zip"));
46
46
  const fast_xml_parser_1 = require("fast-xml-parser");
47
+ const treeStructure_1 = require("../../core/treeStructure");
47
48
  const fs = __importStar(require("fs"));
48
49
  const path = __importStar(require("path"));
49
50
  const child_process_1 = require("child_process");
@@ -394,6 +395,9 @@ function readGrid3History(historyDbPath) {
394
395
  timestamp: (0, dotnetTicks_1.dotNetTicksToDate)(BigInt(row.TickValue ?? 0)),
395
396
  latitude: row.Latitude ?? null,
396
397
  longitude: row.Longitude ?? null,
398
+ type: 'utterance',
399
+ intent: treeStructure_1.AACSemanticIntent.SPEAK_TEXT,
400
+ category: treeStructure_1.AACSemanticCategory.COMMUNICATION,
397
401
  });
398
402
  events.set(phraseId, entry);
399
403
  }