PHPackages                             eloquent-works/rating-kit - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. eloquent-works/rating-kit

ActiveLibrary

eloquent-works/rating-kit
=========================

A comprehensive Laravel rating engine for players, teams, multiplayer competitions, seasons, leaderboards, rating history, and pluggable rating algorithms.

00PHPCI failing

Since Jul 27Pushed todayCompare

[ Source](https://github.com/EloquentWorks/RatingKit)[ Packagist](https://packagist.org/packages/eloquent-works/rating-kit)[ RSS](/packages/eloquent-works-rating-kit/feed)WikiDiscussions main Synced today

READMEChangelogDependenciesVersions (1)Used By (0)

Laravel RatingKit 🏆
===================

[](#laravel-ratingkit-)

[![Tests](https://github.com/EloquentWorks/RatingKit/actions/workflows/tests.yml/badge.svg)](https://github.com/EloquentWorks/RatingKit/actions/workflows/tests.yml)[![Latest Release](https://camo.githubusercontent.com/9cb08da37c235011881ba65126889ee4ee8d9e0c78278a68fcb4f5e144940b62/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f456c6f7175656e74576f726b732f526174696e674b6974)](https://github.com/EloquentWorks/RatingKit/releases)[![License](https://camo.githubusercontent.com/e16cdd7fec34003382e15cdcf091f90851bb774c993d802c02ea3c0b77513cc7/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f456c6f7175656e74576f726b732f526174696e674b6974)](LICENSE)

Laravel RatingKit is a comprehensive, extensible rating engine for Laravel applications.

It supports **1v1, 2v2, 3v3, arbitrary N-vs-N team matches, free-for-alls, and competitions containing multiple teams**. Ratings can be isolated by game, variant, queue, time control, season, region, or any custom pool.

✨ Features
----------

[](#-features)

- Fourteen bundled rating algorithms
- Any number of players per team
- Any number of teams per match
- Free-for-all and ranked-placement results
- Draws and tied ranks
- Weighted participation and substitutes
- Polymorphic `HasRatings` trait for users, teams, bots, clans, and other models
- Model helpers for current ratings, statistics, ranks, histories, matches, and adjustments
- Separate rating pools
- Seasonal ratings
- Provisional ratings
- Rating deviation and volatility
- Rating floors and ceilings
- Win, draw, loss, and streak statistics
- Rating histories and audit trails
- Safe rollback of the latest match
- Idempotent external match IDs
- Direction-aware recurring inactivity decay
- Leaderboards, shared ranks, and conservative leaderboards
- Persistent leaderboard snapshots
- Match outcome prediction and match-quality scoring
- Pool statistics including average, median, minimum, and maximum
- Manual rating adjustments and resets
- Custom Eloquent models and table names
- Numeric, UUID, and ULID rateable model keys
- Per-match algorithm option snapshots
- Pluggable custom algorithms
- Transactional writes and row locking
- Lifecycle events
- Artisan installation, decay, snapshot, and season commands
- PHPUnit, PHPStan, Pint, and GitHub Actions support

🧮 Bundled Algorithms
--------------------

[](#-bundled-algorithms)

KeyAlgorithmTypical use`elo`EloGeneral head-to-head and team competition`elo_mov`Margin-of-victory EloScore-based sports and games`fide`FIDE-style EloChess-style rating pools`uscf`USCF-style EloDevelopment-sensitive chess ratings`glicko`GlickoRatings with uncertainty`glicko2`Glicko-2Uncertainty and volatility tracking`weng_lin`Weng-Lin / OpenSkill-styleBayesian team and multiplayer ratings`bradley_terry`Bradley-TerryPairwise comparison models`plackett_luce`Plackett-LuceOrdered multi-team and free-for-all results`thurstone_mosteller`Thurstone-MostellerGaussian performance modeling`dwz`DWZ-styleDevelopment-sensitive chess ratings`egf`EGF-styleGo-style rating adaptation`ingo`Ingo-styleLower-is-stronger historical rating systems`fifa`FIFA-style EloImportance-weighted team competitionFederation-named drivers are configurable practical adaptations. They are not claims of certification by the named federation.

🚀 Installation
--------------

[](#-installation)

Install the package through Composer:

```
composer require eloquent-works/rating-kit
```

Publish the configuration and migrations, then migrate:

```
php artisan rating-kit:install --migrate
```

Add `HasRatings` to every model that can receive a rating:

```
use EloquentWorks\RatingKit\Traits\HasRatings;

class User extends Authenticatable
{
    use HasRatings;
}
```

The trait gives the model polymorphic rating relationships and convenient helpers:

```
$user->ratings;
$user->ratingFor('chess.blitz', 'glicko2');
$user->currentRating('chess.blitz', 'glicko2');
$user->ratingStats('chess.blitz', 'glicko2');
$user->ratingRank('chess.blitz', 'glicko2');
$user->ratingHistory('chess.blitz', 'glicko2');
$user->recentRatedMatches();
```

A user can own independent ratings for every pool, algorithm, and season. See the full [HasRatings guide](docs/has-ratings.md).

⚡ One Player vs One Player
--------------------------

[](#-one-player-vs-one-player)

```
use EloquentWorks\RatingKit\Facades\RatingKit;

$match = RatingKit::oneVsOne(
    left: $winner,
    right: $loser,
    result: 'left',
    algorithm: 'glicko2',
    pool: 'chess.blitz',
    externalId: 'game-10001',
);
```

Record a draw:

```
RatingKit::oneVsOne(
    left: $playerA,
    right: $playerB,
    result: 'draw',
    algorithm: 'elo',
    pool: 'chess.rapid',
);
```

👥 Two Players vs Two Players
----------------------------

[](#-two-players-vs-two-players)

```
RatingKit::teamVsTeam(
    left: [$playerA, $playerB],
    right: [$playerC, $playerD],
    result: 'left',
    algorithm: 'glicko2',
    pool: 'chess.teams.blitz',
);
```

🛡️ Three Players vs Three Players
---------------------------------

[](#️-three-players-vs-three-players)

```
RatingKit::teamVsTeam(
    left: [$playerA, $playerB, $playerC],
    right: [$playerD, $playerE, $playerF],
    result: 'right',
    algorithm: 'weng_lin',
    pool: 'arena.3v3.ranked',
);
```

The same API supports 4v4, 5v5, 10v10, or any other team size.

🌐 Multiple Teams in One Match
-----------------------------

[](#-multiple-teams-in-one-match)

```
use EloquentWorks\RatingKit\Data\RecordMatch;
use EloquentWorks\RatingKit\Data\Team;

$match = RatingKit::record(new RecordMatch(
    teams: [
        new Team([$a, $b], rank: 1, score: 18, name: 'Red'),
        new Team([$c, $d], rank: 2, score: 14, name: 'Blue'),
        new Team([$e, $f], rank: 3, score: 9, name: 'Green'),
    ],
    algorithm: 'plackett_luce',
    pool: 'arena.trios',
    externalId: 'match-9001',
));
```

Teams may share a rank to represent a tie.

🥇 Free-for-All Results
----------------------

[](#-free-for-all-results)

```
RatingKit::freeForAll([
    ['participant' => $winner, 'rank' => 1, 'score' => 25],
    ['participant' => $second, 'rank' => 2, 'score' => 18],
    ['participant' => $third, 'rank' => 3, 'score' => 12],
    ['participant' => $fourth, 'rank' => 3, 'score' => 12],
], algorithm: 'plackett_luce', pool: 'battle-royale.solo');
```

⚖️ Weighted Participants
------------------------

[](#️-weighted-participants)

Use weights for substitutes, partial participation, handicaps, or unequal contribution:

```
use EloquentWorks\RatingKit\Data\Participant;

RatingKit::teamVsTeam(
    left: [
        new Participant($starter, weight: 1.0),
        new Participant($substitute, weight: 0.35),
    ],
    right: [$opponentA, $opponentB],
    result: 'left',
    algorithm: 'elo',
    pool: 'football.doubles',
);
```

🗂️ Separate Rating Pools
------------------------

[](#️-separate-rating-pools)

The same user can hold independent ratings:

```
$user->currentRating('chess.standard', 'glicko2');
$user->currentRating('chess.blitz', 'glicko2');
$user->currentRating('chess.teams', 'weng_lin');
$user->currentRating('variant.atomic', 'elo');
```

Pools work well for:

- Different games
- Variants
- Time controls
- Ranked and casual queues
- Team sizes
- Regions
- Platforms
- Tournament circuits

📅 Seasons
---------

[](#-seasons)

```
$season = RatingKit::createSeason(
    name: 'Season 1',
    slug: 'season-1',
    pool: 'arena.ranked',
    startsAt: now(),
    endsAt: now()->addMonths(3),
);

RatingKit::teamVsTeam(
    left: $redTeam,
    right: $blueTeam,
    result: 'left',
    algorithm: 'glicko2',
    pool: 'arena.ranked',
    seasonId: $season->id,
);

RatingKit::closeSeason($season);
```

Closing a season automatically captures a final leaderboard for every algorithm used during that season.

📊 Leaderboards
--------------

[](#-leaderboards)

```
$leaderboard = RatingKit::leaderboard(
    pool: 'chess.blitz',
    algorithm: 'glicko2',
    limit: 100,
    includeProvisional: false,
);
```

Use a conservative score based on rating uncertainty:

```
$leaderboard = RatingKit::leaderboard(
    pool: 'chess.blitz',
    algorithm: 'glicko2',
    conservative: true,
);
```

Competitors with the same score share the same competition rank. Retrieve one competitor's rank:

```
$rank = RatingKit::rankOf($user, 'chess.blitz', 'glicko2');
```

🔮 Match Predictions
-------------------

[](#-match-predictions)

```
$prediction = RatingKit::predict([
    new Team([$playerA, $playerB], rank: 1),
    new Team([$playerC, $playerD], rank: 2),
], pool: 'arena.2v2', algorithm: 'glicko2');
```

The result contains a normalized estimated winning share for each team.

Measure how balanced the proposed match is on a scale from `0.0` to `1.0`:

```
$quality = RatingKit::matchQuality([
    new Team([$playerA, $playerB], rank: 1),
    new Team([$playerC, $playerD], rank: 2),
], pool: 'arena.2v2', algorithm: 'glicko2');
```

📈 Pool Statistics
-----------------

[](#-pool-statistics)

```
$statistics = RatingKit::poolStatistics(
    pool: 'chess.blitz',
    algorithm: 'glicko2',
);
```

The result includes total, established, and provisional counts together with average, median, minimum, and maximum ratings.

🧾 Rating History
----------------

[](#-rating-history)

```
$history = $user->ratingHistory('chess.blitz', 'glicko2');
$peak = $user->peakRating('chess.blitz', 'glicko2');
```

Every history record can preserve:

- Rating before and after
- Rating deviation before and after
- Volatility before and after
- Match ID
- Adjustment reason
- Custom metadata

↩️ Rollback and Voiding
-----------------------

[](#️-rollback-and-voiding)

The latest match for every participant can be safely voided:

```
RatingKit::void($match, 'The result was entered incorrectly.');
```

RatingKit refuses an unsafe rollback when one of the participants already has a later result.

🛠️ Manual Adjustments
---------------------

[](#️-manual-adjustments)

```
RatingKit::adjust(
    model: $user,
    amount: 25,
    reason: 'tournament_bonus',
    pool: 'chess.standard',
    algorithm: 'glicko2',
);

RatingKit::setRating($user, 1800, reason: 'verified_import');
RatingKit::resetRating($user);
```

Manual changes are written to rating history.

💤 Inactivity Decay
------------------

[](#-inactivity-decay)

Decay is disabled by default. When enabled, eligible ratings are adjusted once per configured period after the inactivity threshold. Higher-is-better algorithms move down toward the configured minimum; lower-is-better algorithms move up toward the configured maximum.

```
'decay' => [
    'enabled' => true,
    'inactive_after_days' => 90,
    'period_days' => 30,
    'points_per_period' => 10.0,
    'minimum_rating' => 1200.0,
    'maximum_rating' => 2200.0,
],
```

🧩 Custom Algorithms
-------------------

[](#-custom-algorithms)

Implement `RatingAlgorithm` and register it at runtime:

```
RatingKit::algorithms()->extend('my_algorithm', MyAlgorithm::class);
```

Or add it to `config/rating-kit.php`:

```
'algorithms' => [
    'my_algorithm' => App\Ratings\MyAlgorithm::class,
],
```

📣 Events
--------

[](#-events)

RatingKit dispatches events for:

- `MatchRated`
- `MatchVoided`
- `RatingUpdated`
- `RatingAdjusted`
- `SeasonClosed`
- `LeaderboardSnapshotted`

Event dispatching can be disabled in configuration.

🧰 Artisan Commands
------------------

[](#-artisan-commands)

```
php artisan rating-kit:install --migrate
php artisan rating-kit:decay
php artisan rating-kit:snapshot --pool=chess.blitz --algorithm=glicko2
php artisan rating-kit:close-season season-1
```

📚 Documentation
---------------

[](#-documentation)

The complete documentation is available in [`docs/README.md`](docs/README.md).

- [Installation](docs/installation.md)
- [Quick start](docs/quick-start.md)
- [Algorithms](docs/algorithms.md)
- [Team and multiplayer matches](docs/team-matches.md)
- [Pools and variants](docs/rating-pools.md)
- [Seasons](docs/seasons.md)
- [Leaderboards](docs/leaderboards.md)
- [History and rollback](docs/history-and-rollback.md)
- [Configuration](docs/configuration.md)
- [Custom algorithms](docs/custom-algorithms.md)
- [API reference](docs/api-reference.md)

✅ Supported Versions
--------------------

[](#-supported-versions)

- PHP 8.2+
- Laravel 12
- Laravel 13

🧪 Quality Checks
----------------

[](#-quality-checks)

```
composer validate --strict
composer audit
composer format:test
composer analyse
composer test
```

Or run the complete quality suite:

```
composer quality
```

🔐 Security
----------

[](#-security)

Please review [SECURITY.md](SECURITY.md) for responsible vulnerability reporting.

🤝 Contributing
--------------

[](#-contributing)

Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md).

📄 License
---------

[](#-license)

Laravel RatingKit is open-source software licensed under the [MIT license](LICENSE).

###  Health Score

21

—

LowBetter than 17% of packages

Maintenance65

Regular maintenance activity

Popularity0

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/8fed100cd2f03035df142813a4dd6bc1f751bb2db634d7fc59b42790e4029eaf?d=identicon)[omatamix](/maintainers/omatamix)

![](https://avatars.githubusercontent.com/u/287128253?v=4)[Nicholas English](/maintainers/SyntaxPilot)[@SyntaxPilot](https://github.com/SyntaxPilot)

---

Top Contributors

[![SignalByNick](https://avatars.githubusercontent.com/u/47761650?v=4)](https://github.com/SignalByNick "SignalByNick (8 commits)")

---

Tags

elo-rating-algorithmeloquent-workslaravellaravel-packagelaravel-rating-kitrating-system

### Embed Badge

![Health badge](/badges/eloquent-works-rating-kit/health.svg)

```
[![Health](https://phpackages.com/badges/eloquent-works-rating-kit/health.svg)](https://phpackages.com/packages/eloquent-works-rating-kit)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
