From fd556d967f0fa4df1e7fd3d056aa0c1a5532a0f0 Mon Sep 17 00:00:00 2001 From: Nathan Schneider Date: Tue, 30 Jun 2026 16:36:01 -0600 Subject: [PATCH] Fix inverted sign in formal/informal LDA analysis gradient The model (analysis/bicorder_model.json) maps a positive LDA score to cluster 2 = Institutional/Bureaucratic = "formal", but ldaScoreToScale added the score (5 + score*4/3), sending formal/institutional protocols toward 9 (informal) and vice versa. bicorder.json defines this gradient as 1 = formal, 9 = informal, so the score must be subtracted. - Flip the sign: value = 5 - (ldaScore * 4/3); correct the doc comment to state the model's actual sign convention - Rename calculateBureaucratic -> calculateFormalInformal and update the stale analysisOrder comment, matching bicorder.json's formal/informal terms Co-Authored-By: Claude Opus 4.8 (1M context) --- bicorder-app/src/App.svelte | 39 +++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/bicorder-app/src/App.svelte b/bicorder-app/src/App.svelte index fefc16f..d29a4c2 100644 --- a/bicorder-app/src/App.svelte +++ b/bicorder-app/src/App.svelte @@ -60,8 +60,8 @@ }); // Analysis screens (shown in both shortform and longform) - // Show useful/not-useful gradient first (index 3), then the others - const analysisOrder = [3, 0, 1, 2]; // useful/not-useful, hardness, polarization, bureaucratic + // Show the useful gradient first (index 3), then the others + const analysisOrder = [3, 0, 1, 2]; // useful, hardness, polarization, formal/informal analysisOrder.forEach((index) => { screens.push({ type: 'analysis', index, gradient: data.analysis[index] }); }); @@ -303,25 +303,30 @@ function ldaScoreToScale(ldaScore: number | null): number | null { /** - * Convert LDA score to 1-9 scale. - * LDA scores typically range from -4 to +4 (8 range) - * Target scale is 1 to 9 (8 range) + * Convert LDA score to the analysis[2] "formal vs informal" 1-9 scale. + * LDA scores typically range from -4 to +4 (8 range); target is 1-9. * - * Formula: value = 5 + (ldaScore * 4/3) - * - LDA -3 or less → 1 (bureaucratic) - * - LDA 0 → 5 (boundary) - * - LDA +3 or more → 9 (relational) + * The model's sign convention (see analysis/bicorder_model.json): + * positive LDA → cluster 2 = Institutional/Bureaucratic = "formal" + * negative LDA → cluster 1 = Relational/Cultural = "informal" + * bicorder.json defines this gradient as 1 = formal, 9 = informal, so a + * positive LDA score must map toward 1 (formal). The score is therefore + * subtracted, not added. + * + * Formula: value = 5 - (ldaScore * 4/3) + * - LDA +3 or more → 1 (formal / institutional / bureaucratic) + * - LDA 0 → 5 (boundary, characteristics of both families) + * - LDA -3 or less → 9 (informal / relational / cultural) */ if (ldaScore === null) return null; - // Scale: value = 5 + (ldaScore * 1.33) - const value = 5 + (ldaScore * 4.0 / 3.0); + const value = 5 - (ldaScore * 4.0 / 3.0); // Clamp to 1-9 range and round return Math.round(Math.max(1, Math.min(9, value))); } - function calculateBureaucratic(): number | null { + function calculateFormalInformal(): number | null { // Collect all diagnostic gradients with their set and gradient info const ratings: Record = {}; @@ -329,7 +334,7 @@ const setName = diagnosticSet.set_name; diagnosticSet.gradients.forEach((gradient) => { if (gradient.value !== null) { - // Create dimension name in format: SetName_left_vs_right + // Dimension name must match the model's keys: SetName_left_vs_right const dimensionName = `${setName}_${gradient.term_left}_vs_${gradient.term_right}`; ratings[dimensionName] = gradient.value; } @@ -343,10 +348,10 @@ // Get prediction from classifier (need detailed: true to get ldaScore) const result = classifier.predict(ratings, { detailed: true }); - // Convert LDA score to 1-9 scale + // Convert LDA score to the 1-9 formal/informal scale return ldaScoreToScale(result.ldaScore); } catch (error) { - console.error('Error calculating bureaucratic/relational score:', error); + console.error('Error calculating formal/informal score:', error); return null; } } @@ -362,8 +367,8 @@ // Polarized/Centrist data.analysis[1].value = calculatePolarization(); } else if (index === 2) { - // Bureaucratic/Relational (LDA classifier) - data.analysis[2].value = calculateBureaucratic(); + // Formal/Informal (LDA classifier) + data.analysis[2].value = calculateFormalInformal(); } } });