# Scoring System Documentation **Last Updated:** 2026-03-04 ## Overview Drydown uses an **ideal score anchoring** system to calculate personalized review scores. Instead of multiplying weights, the system calculates how close a review is to the user's ideal preferences across all rating properties. ## Core Concept Users set preferences (1-5 scale) that map to their "ideal" ratings. When viewing a review, the system: 1. Calculates the distance between each review property and the user's ideal 2. Applies a power 1.5 curve to convert distance → fit percentage 3. Weights each property by importance 4. Sums weighted fits to produce final score (0-5 scale) ## Preference Mapping ### presenceStyle → Projection/Sillage Ideals | Value | Statement | Ideal Projection | Ideal Sillage | |-------|-----------|------------------|---------------| | 1 | "Skin scent only — just for me" | 1 | 1 | | 2 | "Close company — noticeable only up close" | 2 | 2 | | 3 | "Balanced — depends on the occasion" | 3 | 3 | | 4 | "Room presence — I enjoy being noticed" | 4 | 4 | | 5 | "Announce myself — I like to make an entrance" | 5 | 5 | ### longevityPriority → Longevity Ideal | Value | Statement | Ideal Longevity | |-------|-----------|-----------------| | 1 | "Not important — I enjoy reapplying" | 1 | | 2 | "Nice to have — but not a dealbreaker" | 2 | | 3 | "Moderately important" | 3 | | 4 | "Very important — I want it to last" | 4 | | 5 | "Essential — all-day performance" | 5 | ### complexityPreference → Complexity Ideal | Value | Statement | Ideal Complexity | |-------|-----------|------------------| | 1 | "Simple and focused — clean scents" | 1 | | 2 | "Mostly simple — with maybe a twist" | 2 | | 3 | "Balanced — both simple and complex" | 3 | | 4 | "Layered — discovering notes" | 4 | | 5 | "Intricate — depth and evolution" | 5 | ### scoringApproach → Property Importance Adjustments | Value | Statement | Effect | |-------|-----------|--------| | 1 | "Pure instinct — gut reaction matters most" | `overallRating` importance × 1.3 | | 2 | "Mostly instinct — but I consider details" | `overallRating` importance × 1.3 | | 3 | "Balanced — feeling and analysis equal" | No adjustments | | 4 | "Mostly analytical — weigh each aspect" | Technical properties × 1.1-1.2 | | 5 | "Fully analytical — numbers tell the story" | Technical properties × 1.1-1.2 | **Technical properties boosted (scoringApproach ≥ 4):** - `complexity` × 1.2 - `longevity` × 1.2 - `sillage` × 1.1 - `midProjection` × 1.1 - `openingProjection` × 1.1 ### Quality Rating Ideals All quality ratings always use ideal = 5: - `openingRating` - `drydownRating` - `endRating` - `overallRating` Everyone wants "best" quality, so these properties measure distance from perfection. ## Property Importance Rankings Base importance values (before scoringApproach adjustments): | Rank | Property | Importance | Category | |------|----------|------------|----------| | 1 | midProjection | 1.5 | Performance | | 2 | openingProjection | 1.45 | Performance | | 3 | complexity | 1.4 | Technical | | 4 | longevity | 1.35 | Technical | | 5 | sillage | 1.3 | Performance | | 6 | drydownRating | 1.25 | Quality | | 7 | openingRating | 1.2 | Quality | | 8 | endRating | 1.15 | Quality | | 9 | overallRating | 1.1 | Gut check | **Design rationale:** - Projection metrics matter most (user feedback priority) - Technical properties (complexity, longevity) come next - Quality ratings follow - Overall gut check is least important (captured by other ratings) ## Distance to Fit Conversion The power 1.5 curve penalizes mismatches moderately (more than linear, less than squared): ```typescript normalizedDistance = Math.abs(actual - ideal) / 4 fit = Math.pow(1 - normalizedDistance, 1.5) ``` | Distance | Normalized | Fit % | Interpretation | |----------|------------|-------|----------------| | 0 | 0.00 | 100% | Perfect match | | 1 | 0.25 | 65% | Close match | | 2 | 0.50 | 35% | Moderate mismatch | | 3 | 0.75 | 13% | Poor match | | 4 | 1.00 | 0% | Opposite of ideal | ## Score Calculation Algorithm ```typescript function calculateIdealBasedScore(review, preferences) { // 1. Map preferences → ideal scores const idealScores = preferencesToIdealScores(preferences) // 2. Adjust importance based on scoringApproach const importance = adjustImportanceForScoringApproach( preferences.scoringApproach ) // 3. Calculate weighted fit for each property let totalWeightedFit = 0 let totalImportance = 0 for (const property of ALL_PROPERTIES) { if (review[property] === undefined) continue const distance = Math.abs(review[property] - idealScores[property]) const normalizedDistance = distance / 4 const fit = Math.pow(1 - normalizedDistance, 1.5) const weightedFit = fit * importance[property] totalWeightedFit += weightedFit totalImportance += importance[property] } // 4. Normalize to 0-5 scale const score = (totalWeightedFit / totalImportance) * 5 return Math.round(score * 1000) / 1000 } ``` ## Personalized Score Display Users can toggle between two score perspectives: ### "Their perspective" (scoreLens='theirs') Shows the reviewer's original score (stored in `weightedScore` field). ### "Your perspective" (scoreLens='mine') Recalculates the score using the viewer's ideal preferences. **Special case:** Own reviews always show original score (no recalculation). ## API Reference ### Core Functions #### `calculateIdealBasedScore(review, preferences): number` Calculates score based on distance from user's ideal preferences. **Parameters:** - `review`: Review record with rating properties - `preferences`: `UserPreferencesForScoring` object **Returns:** Score from 0-5 (rounded to 3 decimal places) #### `getPersonalizedScore(review, userPreferences?, isOwnReview): { score, isPersonalized }` Get display score with personalization support. **Parameters:** - `review`: Review record - `userPreferences`: Optional user preferences - `isOwnReview`: Whether review belongs to current user **Returns:** - `score`: Calculated score (0-5) - `isPersonalized`: Whether score was recalculated with user preferences ### Helper Functions #### `preferencesToIdealScores(preferences): IdealScores` Converts user preferences to ideal score values. #### `adjustImportanceForScoringApproach(scoringApproach): PropertyImportance` Adjusts property importance based on scoring approach preference. ## Examples ### Example 1: Matching Preferences **User preferences:** ```typescript { presenceStyle: 5, // "Announce myself" longevityPriority: 5, // "All-day essential" complexityPreference: 4, // "Layered" scoringApproach: 3 // Balanced } ``` **Review ratings:** ```typescript { openingProjection: 5, // Room-filling midProjection: 5, // Strong presence sillage: 5, // Noticeable trail longevity: 5, // All-day lasting complexity: 4, // Very layered // ... other ratings } ``` **Result:** High score (4-5 range) because review closely matches user ideals. ### Example 2: Mismatched Preferences **User preferences:** ```typescript { presenceStyle: 1, // "Skin scent only" longevityPriority: 2, // "Nice to have" complexityPreference: 1, // "Simple and focused" scoringApproach: 1 // "Pure instinct" } ``` **Same review as above** **Result:** Low score (2-3 range) because review is opposite of user ideals (projection distance = 4, complexity distance = 3). ### Example 3: Partial Mismatch **User preferences:** ```typescript { presenceStyle: 5, // Matches review longevityPriority: 1, // "Not important" complexityPreference: 5, // "Intricate" scoringApproach: 3 } ``` **Review ratings:** ```typescript { openingProjection: 5, // ✓ Matches ideal midProjection: 5, // ✓ Matches ideal sillage: 5, // ✓ Matches ideal longevity: 5, // ✗ Doesn't matter (user ideal = 1) complexity: 2, // ✗ Poor match (user ideal = 5) // ... other ratings } ``` **Result:** Moderate score (3-4 range). Projection matches perfectly, but longevity "overperformance" and low complexity reduce fit. ## Migration from Weight-Based System No migration needed! The new system: - Uses the same 4 preferences (no schema changes) - Keeps the same UI (no interface changes) - Maintains backward compatibility (existing data works unchanged) The only change is how preferences are interpreted by the scoring algorithm. ## See Also - [Implementation Summary](../IMPLEMENTATION_SUMMARY.md) - [Preference Definitions](../src/data/preferenceDefinitions.ts) - [Review Utilities](../src/utils/reviewUtils.ts)