@@ -118,12 +118,27 @@ export class MetricsCalculator {
118118 // Update buttons using dynamic spelling effort if applicable
119119 const buttons = Array . from ( knownButtons . values ( ) ) . sort ( ( a , b ) => a . effort - b . effort ) ;
120120
121- // Calculate metrics for word forms (smart grammar predictions)
122- const wordFormMetrics = this . calculateWordFormMetrics ( tree , buttons , options ) ;
123- buttons . push ( ...wordFormMetrics ) ;
121+ // Calculate metrics for word forms (smart grammar predictions) if enabled
122+ // Default to true if not specified
123+ const useSmartGrammar = options . useSmartGrammar !== false ;
124+ if ( useSmartGrammar ) {
125+ const { wordFormMetrics, replacedLabels } = this . calculateWordFormMetrics (
126+ tree ,
127+ buttons ,
128+ options
129+ ) ;
130+
131+ // Remove buttons that were replaced by lower-effort word forms
132+ const filteredButtons = buttons . filter ( ( btn ) => ! replacedLabels . has ( btn . label . toLowerCase ( ) ) ) ;
124133
125- // Re-sort after adding word forms
126- buttons . sort ( ( a , b ) => a . effort - b . effort ) ;
134+ // Add word forms and re-sort
135+ filteredButtons . push ( ...wordFormMetrics ) ;
136+ filteredButtons . sort ( ( a , b ) => a . effort - b . effort ) ;
137+
138+ // Replace the original buttons array
139+ buttons . length = 0 ;
140+ buttons . push ( ...filteredButtons ) ;
141+ }
127142
128143 // Calculate grid dimensions
129144 const grid = this . calculateGridDimensions ( tree ) ;
@@ -734,19 +749,23 @@ export class MetricsCalculator {
734749 * - Parent button's cumulative effort (to reach the button)
735750 * - + Effort to select the word form from its position in predictions grid
736751 *
752+ * If a word exists as both a regular button and a word form, the version
753+ * with lower effort is kept.
754+ *
737755 * @param tree - The AAC tree
738756 * @param buttons - Already calculated button metrics
739757 * @param options - Metrics options
740- * @returns Array of word form button metrics
758+ * @returns Object containing word form metrics and labels that were replaced
741759 */
742760 private calculateWordFormMetrics (
743761 tree : AACTree ,
744762 buttons : ButtonMetrics [ ] ,
745763 _options : MetricsOptions = { }
746- ) : ButtonMetrics [ ] {
764+ ) : { wordFormMetrics : ButtonMetrics [ ] ; replacedLabels : Set < string > } {
747765 const wordFormMetrics : ButtonMetrics [ ] = [ ] ;
766+ const replacedLabels = new Set < string > ( ) ;
748767
749- // Track which buttons already exist to avoid duplicates
768+ // Track buttons by label to compare efforts
750769 const existingLabels = new Map < string , ButtonMetrics > ( ) ;
751770 buttons . forEach ( ( btn ) => existingLabels . set ( btn . label . toLowerCase ( ) , btn ) ) ;
752771
@@ -764,11 +783,6 @@ export class MetricsCalculator {
764783 btn . predictions . forEach ( ( wordForm : string , index : number ) => {
765784 const wordFormLower = wordForm . toLowerCase ( ) ;
766785
767- // Skip if this word form already exists as a regular button
768- if ( existingLabels . has ( wordFormLower ) ) {
769- return ;
770- }
771-
772786 // Calculate effort based on position in predictions array
773787 // Assume predictions are displayed in a grid layout (e.g., 2 columns)
774788 const predictionsGridCols = 2 ; // Typical predictions layout
@@ -784,7 +798,20 @@ export class MetricsCalculator {
784798 // Word form effort = parent button's cumulative effort + selection effort
785799 const wordFormEffort = parentMetrics . effort + predictionSelectionEffort ;
786800
787- // Mark as word form with a special ID pattern
801+ // Check if this word already exists as a regular button
802+ const existingBtn = existingLabels . get ( wordFormLower ) ;
803+
804+ // If word exists and has lower or equal effort, skip the word form
805+ if ( existingBtn && existingBtn . effort <= wordFormEffort ) {
806+ return ;
807+ }
808+
809+ // If word exists but word form has lower effort, mark for replacement
810+ if ( existingBtn && existingBtn . effort > wordFormEffort ) {
811+ replacedLabels . add ( wordFormLower ) ;
812+ }
813+
814+ // Create word form metric
788815 const wordFormBtn : ButtonMetrics = {
789816 id : `${ btn . id } _wordform_${ index } ` ,
790817 label : wordForm ,
@@ -806,9 +833,14 @@ export class MetricsCalculator {
806833 } ) ;
807834 } ) ;
808835
809- console . log ( `📝 Calculated ${ wordFormMetrics . length } word form metrics from predictions` ) ;
836+ console . log (
837+ `📝 Calculated ${ wordFormMetrics . length } word form metrics` +
838+ ( replacedLabels . size > 0
839+ ? ` (${ replacedLabels . size } replaced higher-effort buttons: ${ Array . from ( replacedLabels ) . join ( ', ' ) } )`
840+ : '' )
841+ ) ;
810842
811- return wordFormMetrics ;
843+ return { wordFormMetrics, replacedLabels } ;
812844 }
813845
814846 /**
0 commit comments