a a vibe-coded abomination experiment of a fragrance review platform built on the atmosphere. drydown.social
at main 277 lines 8.8 kB view raw view rendered
1# Scoring System Documentation 2 3**Last Updated:** 2026-03-04 4 5## Overview 6 7Drydown 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. 8 9## Core Concept 10 11Users set preferences (1-5 scale) that map to their "ideal" ratings. When viewing a review, the system: 121. Calculates the distance between each review property and the user's ideal 132. Applies a power 1.5 curve to convert distance → fit percentage 143. Weights each property by importance 154. Sums weighted fits to produce final score (0-5 scale) 16 17## Preference Mapping 18 19### presenceStyle → Projection/Sillage Ideals 20 21| Value | Statement | Ideal Projection | Ideal Sillage | 22|-------|-----------|------------------|---------------| 23| 1 | "Skin scent only — just for me" | 1 | 1 | 24| 2 | "Close company — noticeable only up close" | 2 | 2 | 25| 3 | "Balanced — depends on the occasion" | 3 | 3 | 26| 4 | "Room presence — I enjoy being noticed" | 4 | 4 | 27| 5 | "Announce myself — I like to make an entrance" | 5 | 5 | 28 29### longevityPriority → Longevity Ideal 30 31| Value | Statement | Ideal Longevity | 32|-------|-----------|-----------------| 33| 1 | "Not important — I enjoy reapplying" | 1 | 34| 2 | "Nice to have — but not a dealbreaker" | 2 | 35| 3 | "Moderately important" | 3 | 36| 4 | "Very important — I want it to last" | 4 | 37| 5 | "Essential — all-day performance" | 5 | 38 39### complexityPreference → Complexity Ideal 40 41| Value | Statement | Ideal Complexity | 42|-------|-----------|------------------| 43| 1 | "Simple and focused — clean scents" | 1 | 44| 2 | "Mostly simple — with maybe a twist" | 2 | 45| 3 | "Balanced — both simple and complex" | 3 | 46| 4 | "Layered — discovering notes" | 4 | 47| 5 | "Intricate — depth and evolution" | 5 | 48 49### scoringApproach → Property Importance Adjustments 50 51| Value | Statement | Effect | 52|-------|-----------|--------| 53| 1 | "Pure instinct — gut reaction matters most" | `overallRating` importance × 1.3 | 54| 2 | "Mostly instinct — but I consider details" | `overallRating` importance × 1.3 | 55| 3 | "Balanced — feeling and analysis equal" | No adjustments | 56| 4 | "Mostly analytical — weigh each aspect" | Technical properties × 1.1-1.2 | 57| 5 | "Fully analytical — numbers tell the story" | Technical properties × 1.1-1.2 | 58 59**Technical properties boosted (scoringApproach ≥ 4):** 60- `complexity` × 1.2 61- `longevity` × 1.2 62- `sillage` × 1.1 63- `midProjection` × 1.1 64- `openingProjection` × 1.1 65 66### Quality Rating Ideals 67 68All quality ratings always use ideal = 5: 69- `openingRating` 70- `drydownRating` 71- `endRating` 72- `overallRating` 73 74Everyone wants "best" quality, so these properties measure distance from perfection. 75 76## Property Importance Rankings 77 78Base importance values (before scoringApproach adjustments): 79 80| Rank | Property | Importance | Category | 81|------|----------|------------|----------| 82| 1 | midProjection | 1.5 | Performance | 83| 2 | openingProjection | 1.45 | Performance | 84| 3 | complexity | 1.4 | Technical | 85| 4 | longevity | 1.35 | Technical | 86| 5 | sillage | 1.3 | Performance | 87| 6 | drydownRating | 1.25 | Quality | 88| 7 | openingRating | 1.2 | Quality | 89| 8 | endRating | 1.15 | Quality | 90| 9 | overallRating | 1.1 | Gut check | 91 92**Design rationale:** 93- Projection metrics matter most (user feedback priority) 94- Technical properties (complexity, longevity) come next 95- Quality ratings follow 96- Overall gut check is least important (captured by other ratings) 97 98## Distance to Fit Conversion 99 100The power 1.5 curve penalizes mismatches moderately (more than linear, less than squared): 101 102```typescript 103normalizedDistance = Math.abs(actual - ideal) / 4 104fit = Math.pow(1 - normalizedDistance, 1.5) 105``` 106 107| Distance | Normalized | Fit % | Interpretation | 108|----------|------------|-------|----------------| 109| 0 | 0.00 | 100% | Perfect match | 110| 1 | 0.25 | 65% | Close match | 111| 2 | 0.50 | 35% | Moderate mismatch | 112| 3 | 0.75 | 13% | Poor match | 113| 4 | 1.00 | 0% | Opposite of ideal | 114 115## Score Calculation Algorithm 116 117```typescript 118function calculateIdealBasedScore(review, preferences) { 119 // 1. Map preferences → ideal scores 120 const idealScores = preferencesToIdealScores(preferences) 121 122 // 2. Adjust importance based on scoringApproach 123 const importance = adjustImportanceForScoringApproach( 124 preferences.scoringApproach 125 ) 126 127 // 3. Calculate weighted fit for each property 128 let totalWeightedFit = 0 129 let totalImportance = 0 130 131 for (const property of ALL_PROPERTIES) { 132 if (review[property] === undefined) continue 133 134 const distance = Math.abs(review[property] - idealScores[property]) 135 const normalizedDistance = distance / 4 136 const fit = Math.pow(1 - normalizedDistance, 1.5) 137 const weightedFit = fit * importance[property] 138 139 totalWeightedFit += weightedFit 140 totalImportance += importance[property] 141 } 142 143 // 4. Normalize to 0-5 scale 144 const score = (totalWeightedFit / totalImportance) * 5 145 return Math.round(score * 1000) / 1000 146} 147``` 148 149## Personalized Score Display 150 151Users can toggle between two score perspectives: 152 153### "Their perspective" (scoreLens='theirs') 154Shows the reviewer's original score (stored in `weightedScore` field). 155 156### "Your perspective" (scoreLens='mine') 157Recalculates the score using the viewer's ideal preferences. 158 159**Special case:** Own reviews always show original score (no recalculation). 160 161## API Reference 162 163### Core Functions 164 165#### `calculateIdealBasedScore(review, preferences): number` 166Calculates score based on distance from user's ideal preferences. 167 168**Parameters:** 169- `review`: Review record with rating properties 170- `preferences`: `UserPreferencesForScoring` object 171 172**Returns:** Score from 0-5 (rounded to 3 decimal places) 173 174#### `getPersonalizedScore(review, userPreferences?, isOwnReview): { score, isPersonalized }` 175Get display score with personalization support. 176 177**Parameters:** 178- `review`: Review record 179- `userPreferences`: Optional user preferences 180- `isOwnReview`: Whether review belongs to current user 181 182**Returns:** 183- `score`: Calculated score (0-5) 184- `isPersonalized`: Whether score was recalculated with user preferences 185 186### Helper Functions 187 188#### `preferencesToIdealScores(preferences): IdealScores` 189Converts user preferences to ideal score values. 190 191#### `adjustImportanceForScoringApproach(scoringApproach): PropertyImportance` 192Adjusts property importance based on scoring approach preference. 193 194## Examples 195 196### Example 1: Matching Preferences 197 198**User preferences:** 199```typescript 200{ 201 presenceStyle: 5, // "Announce myself" 202 longevityPriority: 5, // "All-day essential" 203 complexityPreference: 4, // "Layered" 204 scoringApproach: 3 // Balanced 205} 206``` 207 208**Review ratings:** 209```typescript 210{ 211 openingProjection: 5, // Room-filling 212 midProjection: 5, // Strong presence 213 sillage: 5, // Noticeable trail 214 longevity: 5, // All-day lasting 215 complexity: 4, // Very layered 216 // ... other ratings 217} 218``` 219 220**Result:** High score (4-5 range) because review closely matches user ideals. 221 222### Example 2: Mismatched Preferences 223 224**User preferences:** 225```typescript 226{ 227 presenceStyle: 1, // "Skin scent only" 228 longevityPriority: 2, // "Nice to have" 229 complexityPreference: 1, // "Simple and focused" 230 scoringApproach: 1 // "Pure instinct" 231} 232``` 233 234**Same review as above** 235 236**Result:** Low score (2-3 range) because review is opposite of user ideals (projection distance = 4, complexity distance = 3). 237 238### Example 3: Partial Mismatch 239 240**User preferences:** 241```typescript 242{ 243 presenceStyle: 5, // Matches review 244 longevityPriority: 1, // "Not important" 245 complexityPreference: 5, // "Intricate" 246 scoringApproach: 3 247} 248``` 249 250**Review ratings:** 251```typescript 252{ 253 openingProjection: 5, // ✓ Matches ideal 254 midProjection: 5, // ✓ Matches ideal 255 sillage: 5, // ✓ Matches ideal 256 longevity: 5, // ✗ Doesn't matter (user ideal = 1) 257 complexity: 2, // ✗ Poor match (user ideal = 5) 258 // ... other ratings 259} 260``` 261 262**Result:** Moderate score (3-4 range). Projection matches perfectly, but longevity "overperformance" and low complexity reduce fit. 263 264## Migration from Weight-Based System 265 266No migration needed! The new system: 267- Uses the same 4 preferences (no schema changes) 268- Keeps the same UI (no interface changes) 269- Maintains backward compatibility (existing data works unchanged) 270 271The only change is how preferences are interpreted by the scoring algorithm. 272 273## See Also 274 275- [Implementation Summary](../IMPLEMENTATION_SUMMARY.md) 276- [Preference Definitions](../src/data/preferenceDefinitions.ts) 277- [Review Utilities](../src/utils/reviewUtils.ts)