Metadata-Version: 2.4
Name: ltp-rs
Version: 0.1.0
Summary: Long-Term Proportionality: fair sequential group recommendation via proportional, cross-session selection.
Project-URL: Homepage, https://github.com/pdokoupil/LTP
Project-URL: Repository, https://github.com/pdokoupil/LTP
Project-URL: Paper, https://doi.org/10.1007/s10115-025-02642-9
Author: Patrik Dokoupil
License: MIT
License-File: LICENSE
Keywords: fairness,group-recommendation,long-term-fairness,proportionality,recommender-systems,sequential-recommendation
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.9
Requires-Dist: numpy>=1.21
Requires-Dist: rlprop-rs>=0.1
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == 'dev'
Description-Content-Type: text/markdown

<div align="center">

# LTP

**Group recommendations that are fair *over time*.**
If a member loses out this session, LTP prioritizes them next session — keeping
result-level proportionality across a sequence of sessions while preserving group
utility.

<!-- Badges light up once the repo is pushed and the package is published to PyPI. -->
[![PyPI](https://img.shields.io/pypi/v/ltp-rs.svg)](https://pypi.org/project/ltp-rs/)
[![CI](https://github.com/pdokoupil/LTP/actions/workflows/ci.yml/badge.svg)](https://github.com/pdokoupil/LTP/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![Paper](https://img.shields.io/badge/paper-KAIS'26-b31b1b.svg)](https://doi.org/10.1007/s10115-025-02642-9)

</div>

Group recommender systems must balance conflicting member preferences, and within
a *single* recommendation it's often impossible to be fair to everyone. But group
decisions usually happen over **several sessions** — so fairness can be balanced
*across* them. `LTP` (Long-Term Proportionality) does exactly that: it carries
per-member fairness state between sessions and selects proportionally, so no member
is consistently overlooked.

> **Algorithm:** Patrik Dokoupil & Ladislav Peska (KAIS '26, see [Citing](#citing)).
> **This implementation:** Patrik Dokoupil — a standalone, packaged reimplementation.

## Install

```bash
pip install ltp-rs
```

## Quick start

Create one `LTP` per group; call `recommend` once per session — the fairness state
carries across calls.

```python
import numpy as np
from ltp import LTP

agg = LTP(gamma=1.0, beta=0.0)          # one aggregator per group

for session in range(n_sessions):
    # ratings[m, i] = predicted rating of item i for group member m, this session
    ratings = your_model.predict(group, session)     # shape (n_members, n_items)
    recommendation = agg.recommend(ratings, k=10)    # -> item indices

agg.reset()                              # start a fresh group
```

Items recommended in earlier sessions are not repeated (toggle with
`avoid_repeats=False`).

## What you can tune

- **`gamma`** — memory of past sessions (1.0 = full; < 1.0 gradually forgets).
- **`beta`** — in-list position discount; item at position `p` updates state with
  weight `exp(-beta·p)` (0.0 = all positions equal).
- **`tie_breaking`** — fairness nudge when items score near-equally.
- **`member_weights`** — relative importance of members (the paper's user weights
  `w`; default uniform).
- **`normalize`** — per-member scaling each session: `None`, `"minmax"`,
  `"standard"`, `"robust"`, `"quantile"`.

## How it works

Each session, LTP runs RLProp's mandate-allocation step over the **group members**
(members play the role of "objectives"), but seeded with each member's accumulated
gains from previous sessions, averaged by the number of sessions. A member who has
been under-served carries a larger unfilled mandate and is favored until balance is
restored. See the [paper](https://doi.org/10.1007/s10115-025-02642-9) for the
algorithm, the simulated-choice evaluation, and the fairness/utility trade-off.

`python examples/quickstart.py` shows cumulative per-member utility staying far more
balanced under LTP than under a per-session additive baseline.

## Relation to RLProp

LTP is the **sequential, group** generalization of
[RLProp](https://github.com/pdokoupil/rlprop): a single session of LTP with
`gamma=1, beta=0` is exactly RLProp over the group members. This package reuses
RLProp's allocation kernel (`rlprop.core.rlprop_step`) and normalizers, so the two
stay numerically consistent (a test asserts the single-session equivalence).

## Citing

If you use this software, please cite the paper (GitHub's "Cite this repository"
button reads [`CITATION.cff`](CITATION.cff)):

```bibtex
@article{dokoupil2026ltp,
  author  = {Dokoupil, Patrik and Peska, Ladislav},
  title   = {Long-term fairness in sequential group recommendations},
  journal = {Knowledge and Information Systems},
  volume  = {68},
  number  = {1},
  year    = {2026},
  doi     = {10.1007/s10115-025-02642-9}
}
```

## License

MIT — see [`LICENSE`](LICENSE).
