> ## Documentation Index
> Fetch the complete documentation index at: https://devperez08-platform-list-anime-54.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Anime Scoring System

> Rate and score your anime collection with EpiNeko's integrated scoring system

## Overview

EpiNeko includes a scoring system that allows users to rate anime in their library on a scale of 0-10, matching the MyAnimeList rating standard. Scores help you remember which series you enjoyed most and provide personal ratings for your collection.

<CardGroup cols={2}>
  <Card title="0-10 Scale" icon="star-half-stroke">
    Rate anime using the standard 0-10 point scale
  </Card>

  <Card title="Optional Rating" icon="star">
    Scoring is optional - rate only what you want
  </Card>

  <Card title="Database Validated" icon="shield-check">
    Scores validated at database level for integrity
  </Card>

  <Card title="Library Integration" icon="book">
    Scores stored with your library items
  </Card>
</CardGroup>

## Score Field

The score is an optional integer field in library items:

```typescript theme={null}
export interface LibraryItem {
  id?: string;
  user_id?: string;
  anime_id_jikan: number;
  title: string;
  image_url?: string;
  status: LibraryStatus;
  score?: number;              // Rating from 0-10
  episodes_watched?: number;
}
```

**Database Schema** (supabase/migrations/20260218\_initial\_schema.sql:54):

```sql theme={null}
create table public.user_library (
  -- ...
  score integer check (score >= 0 and score <= 10),
  -- ...
);
```

<Note>
  The database constraint ensures scores are always between 0 and 10 inclusive. Invalid scores are rejected at the database level.
</Note>

## Rating Scale

EpiNeko uses the standard 0-10 rating scale:

| Score | Rating      | Description          |
| ----- | ----------- | -------------------- |
| 10    | Masterpiece | Perfect in every way |
| 9     | Great       | Exceptional quality  |
| 8     | Very Good   | Highly enjoyable     |
| 7     | Good        | Worth watching       |
| 6     | Fine        | Decent but flawed    |
| 5     | Average     | Neither good nor bad |
| 4     | Bad         | Below average        |
| 3     | Very Bad    | Significant problems |
| 2     | Horrible    | Almost unwatchable   |
| 1     | Appalling   | Completely terrible  |
| 0     | Unrated     | Not scored yet       |

<Tip>
  This scale matches MyAnimeList's rating system, making it familiar to anime fans and allowing for easy comparison.
</Tip>

## Adding Scores

### Via updateLibraryItem

Add or update a score for any anime in your library:

```typescript theme={null}
import { updateLibraryItem } from '@/services/library';

// Add score to existing library item
await updateLibraryItem(52991, { 
  score: 9 
});

// Update score along with status
await updateLibraryItem(52991, { 
  status: 'completed',
  score: 9,
  episodes_watched: 28
});
```

### When Adding to Library

Include score when first adding anime:

```typescript theme={null}
import { addToLibrary, LibraryItem } from '@/services/library';

const item: LibraryItem = {
  anime_id_jikan: 52991,
  title: "Sousou no Frieren",
  image_url: "https://cdn.myanimelist.net/images/anime/...",
  status: 'completed',
  episodes_watched: 28,
  score: 9  // Initial rating
};

await addToLibrary(item);
```

### Score Without Completing

You can score anime even if not completed:

```typescript theme={null}
// Rate after dropping
await updateLibraryItem(animeId, {
  status: 'dropped',
  episodes_watched: 5,
  score: 4  // Rate what you watched
});

// Rate while watching
await updateLibraryItem(animeId, {
  status: 'watching',
  episodes_watched: 12,
  score: 8  // Rate based on episodes so far
});
```

<Warning>
  While you can score incomplete anime, it's recommended to score only completed series for the most accurate ratings.
</Warning>

## Removing Scores

Set score to null or undefined to remove:

```typescript theme={null}
// Remove score
await updateLibraryItem(animeId, { 
  score: null 
});

// Or omit score field entirely
await updateLibraryItem(animeId, { 
  status: 'watching' 
  // score remains unchanged
});
```

## Displaying Scores

### In Library Cards

Show user's personal score on library anime cards:

```tsx theme={null}
import AnimeCard from '@/components/anime/AnimeCard';

<AnimeCard 
  mal_id={item.anime_id_jikan}
  image={item.image_url}
  title={item.title}
  score={item.score?.toString()}  // Personal score
/>
```

**AnimeCard Component** (src/components/anime/AnimeCard.tsx:31-35):

```tsx theme={null}
{score && (
  <div className="absolute top-3 right-3 bg-black/60 backdrop-blur-md 
                  px-2 py-1 rounded-lg border border-white/10">
    <span className="text-yellow-400 text-xs font-bold">
      ★ {score}
    </span>
  </div>
)}
```

### MAL Score vs Personal Score

Distinguish between MyAnimeList's score and user's personal score:

```typescript theme={null}
import { JikanAnime } from '@/services/jikan';

interface AnimeDisplay {
  malScore: number | null;      // From Jikan API
  personalScore?: number;       // From user's library
}

// Display both
console.log(`MAL Score: ${anime.score ?? 'N/A'}`);
console.log(`Your Score: ${libraryItem.score ?? 'Not rated'}`);
```

**Example Display:**

```tsx theme={null}
<div className="scores">
  <div className="mal-score">
    <span className="label">MAL Average</span>
    <span className="value">★ {anime.score || 'N/A'}</span>
  </div>
  
  {libraryItem?.score && (
    <div className="personal-score">
      <span className="label">Your Rating</span>
      <span className="value">★ {libraryItem.score}</span>
    </div>
  )}
</div>
```

## Score Input Components

### Simple Dropdown

Basic score selector:

```tsx theme={null}
<select 
  value={score || 0}
  onChange={(e) => handleScoreChange(Number(e.target.value))}
>
  <option value={0}>Not Rated</option>
  {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(n => (
    <option key={n} value={n}>{n} - {getRatingLabel(n)}</option>
  ))}
</select>
```

### Star Rating Input

Interactive star-based rating:

```tsx theme={null}
const StarRating = ({ value, onChange }: { value: number, onChange: (n: number) => void }) => {
  return (
    <div className="flex gap-1">
      {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(n => (
        <button
          key={n}
          onClick={() => onChange(n)}
          className={n <= value ? 'text-yellow-400' : 'text-zinc-600'}
        >
          ★
        </button>
      ))}
    </div>
  );
};
```

### Slider Input

Slider for granular control:

```tsx theme={null}
<input
  type="range"
  min="0"
  max="10"
  step="1"
  value={score || 0}
  onChange={(e) => handleScoreChange(Number(e.target.value))}
  className="range range-primary"
/>
<div className="text-center text-2xl font-bold">
  {score ? `★ ${score}` : 'Not Rated'}
</div>
```

## Score Statistics

Calculate statistics from your library scores:

```typescript theme={null}
import { getLibrary } from '@/services/library';

const library = await getLibrary();

// Filter scored items
const scoredItems = library.filter(item => item.score !== null && item.score !== undefined);

// Calculate average score
const averageScore = scoredItems.reduce((sum, item) => sum + (item.score || 0), 0) / scoredItems.length;

console.log(`Average Score: ${averageScore.toFixed(2)}`);
console.log(`Rated: ${scoredItems.length} / ${library.length}`);
```

### Score Distribution

Analyze your rating patterns:

```typescript theme={null}
const scoreDistribution = {
  masterpiece: library.filter(i => i.score === 10).length,
  great: library.filter(i => i.score === 9).length,
  veryGood: library.filter(i => i.score === 8).length,
  good: library.filter(i => i.score === 7).length,
  fine: library.filter(i => i.score === 6).length,
  average: library.filter(i => i.score === 5).length,
  belowAverage: library.filter(i => i.score && i.score < 5).length,
};

// Find your highest rated anime
const topRated = library
  .filter(i => i.score)
  .sort((a, b) => (b.score || 0) - (a.score || 0))
  .slice(0, 10);
```

### Visualization

Display score distribution chart:

```tsx theme={null}
const ScoreDistribution = ({ library }: { library: LibraryItem[] }) => {
  const scores = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
  
  return (
    <div className="space-y-2">
      {scores.map(score => {
        const count = library.filter(i => i.score === score).length;
        const percentage = (count / library.length) * 100;
        
        return (
          <div key={score} className="flex items-center gap-4">
            <span className="w-8 text-right font-bold">{score}</span>
            <div className="flex-1 h-6 bg-zinc-800 rounded-full overflow-hidden">
              <div 
                className="h-full bg-yellow-400"
                style={{ width: `${percentage}%` }}
              />
            </div>
            <span className="w-12 text-zinc-400">{count}</span>
          </div>
        );
      })}
    </div>
  );
};
```

## Scoring Recommendations

### Best Practices

<CardGroup cols={2}>
  <Card title="Score After Completion" icon="check-circle">
    Rate anime after finishing for most accurate scores
  </Card>

  <Card title="Use Full Scale" icon="chart-column">
    Don't be afraid to use the entire 0-10 range
  </Card>

  <Card title="Be Consistent" icon="equals">
    Develop personal criteria for each score level
  </Card>

  <Card title="Update Scores" icon="arrows-rotate">
    Revisit and adjust scores as your tastes evolve
  </Card>
</CardGroup>

### Personal Rating Guidelines

Develop your own rating criteria:

<Tabs>
  <Tab title="Story-Focused">
    * 10: Perfect narrative
    * 8-9: Excellent storytelling
    * 6-7: Good but flawed plot
    * 4-5: Weak story
    * 1-3: Poor narrative
  </Tab>

  <Tab title="Entertainment">
    * 10: Maximum enjoyment
    * 8-9: Highly entertaining
    * 6-7: Fun to watch
    * 4-5: Somewhat boring
    * 1-3: Not entertaining
  </Tab>

  <Tab title="Overall Quality">
    * 10: Perfect in all aspects
    * 8-9: Minor flaws only
    * 6-7: Some issues
    * 4-5: Major problems
    * 1-3: Fundamentally flawed
  </Tab>
</Tabs>

## Database Validation

Scores are validated at multiple levels:

### Database Constraint

PostgreSQL check constraint:

```sql theme={null}
score integer check (score >= 0 and score <= 10)
```

**Behavior:**

* Rejects scores \< 0
* Rejects scores > 10
* Accepts null (no score)
* Accepts 0-10 inclusive

### Application Validation

Add client-side validation:

```typescript theme={null}
const validateScore = (score: number | null | undefined): boolean => {
  if (score === null || score === undefined) return true; // Null is valid
  return score >= 0 && score <= 10 && Number.isInteger(score);
};

// Before saving
if (!validateScore(score)) {
  throw new Error('Score must be an integer between 0 and 10');
}

await updateLibraryItem(animeId, { score });
```

### Type Safety

Use TypeScript for type safety:

```typescript theme={null}
type Score = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | null | undefined;

interface LibraryItem {
  // ...
  score?: Score;
}
```

## Score-Based Features

### Filtering by Score

Filter library by rating:

```typescript theme={null}
const library = await getLibrary();

// Highly rated anime (8+)
const highlyRated = library.filter(item => item.score && item.score >= 8);

// Low rated (< 5)
const lowRated = library.filter(item => item.score && item.score < 5);

// Unrated
const unrated = library.filter(item => !item.score);
```

### Sorting by Score

Sort library by personal ratings:

```typescript theme={null}
// Highest rated first
const sortedByScore = library.sort((a, b) => 
  (b.score || 0) - (a.score || 0)
);

// Lowest rated first
const lowestFirst = library.sort((a, b) => 
  (a.score || 0) - (b.score || 0)
);

// Unrated first, then by score
const unratedFirst = library.sort((a, b) => {
  if (!a.score && !b.score) return 0;
  if (!a.score) return -1;
  if (!b.score) return 1;
  return b.score - a.score;
});
```

### Recommendations

Find similar highly-rated anime:

```typescript theme={null}
const getRecommendations = async (animeId: number) => {
  const library = await getLibrary();
  const targetItem = library.find(i => i.anime_id_jikan === animeId);
  
  if (!targetItem?.score) return [];
  
  // Find anime with similar scores
  return library.filter(item => 
    item.anime_id_jikan !== animeId &&
    item.score &&
    Math.abs(item.score - targetItem.score) <= 1
  );
};
```

## Comparing Scores

### MAL Score Comparison

Compare your rating to MAL average:

```typescript theme={null}
interface ScoreComparison {
  anime: JikanAnime;
  malScore: number | null;
  personalScore: number | null;
  difference: number | null;
}

const compareScores = (anime: JikanAnime, libraryItem?: LibraryItem): ScoreComparison => {
  const malScore = anime.score;
  const personalScore = libraryItem?.score || null;
  
  const difference = malScore && personalScore 
    ? personalScore - malScore 
    : null;
  
  return {
    anime,
    malScore,
    personalScore,
    difference
  };
};

// Usage
const comparison = compareScores(anime, libraryItem);
console.log(`Your score is ${comparison.difference} points ${
  comparison.difference > 0 ? 'higher' : 'lower'
} than MAL average`);
```

### Score Agreement Analysis

Analyze how often you agree with MAL:

```typescript theme={null}
const analyzeAgreement = (library: LibraryItem[], animeData: JikanAnime[]) => {
  let agreements = 0;
  let disagreements = 0;
  
  library.forEach(item => {
    const anime = animeData.find(a => a.mal_id === item.anime_id_jikan);
    if (!anime?.score || !item.score) return;
    
    const diff = Math.abs(item.score - anime.score);
    if (diff <= 1) {
      agreements++;
    } else {
      disagreements++;
    }
  });
  
  return {
    agreementRate: (agreements / (agreements + disagreements)) * 100,
    agreements,
    disagreements
  };
};
```

## Export Scores

Export your ratings for backup or analysis:

```typescript theme={null}
const exportScores = async () => {
  const library = await getLibrary();
  
  const scores = library
    .filter(item => item.score)
    .map(item => ({
      animeId: item.anime_id_jikan,
      title: item.title,
      score: item.score,
      status: item.status
    }));
  
  // Convert to CSV
  const csv = [
    'Anime ID,Title,Score,Status',
    ...scores.map(s => `${s.animeId},"${s.title}",${s.score},${s.status}`)
  ].join('\n');
  
  return csv;
};
```

## Related Features

* [Library Management](/features/library-management) - Score storage
* [Watch Status](/features/watch-status) - Status affects when to score
* [Profile Statistics](/features/profile-settings) - View score statistics
* [Progress Tracking](/features/progress-tracking) - Track before scoring
