tiebreak-core 1.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. tiebreak_core-1.2.0/LICENSE +21 -0
  2. tiebreak_core-1.2.0/PKG-INFO +279 -0
  3. tiebreak_core-1.2.0/README.md +255 -0
  4. tiebreak_core-1.2.0/pyproject.toml +39 -0
  5. tiebreak_core-1.2.0/setup.cfg +4 -0
  6. tiebreak_core-1.2.0/src/tiebreak_core/__init__.py +240 -0
  7. tiebreak_core-1.2.0/src/tiebreak_core/article16.py +140 -0
  8. tiebreak_core-1.2.0/src/tiebreak_core/calculators.py +350 -0
  9. tiebreak_core-1.2.0/src/tiebreak_core/display.py +56 -0
  10. tiebreak_core-1.2.0/src/tiebreak_core/errors.py +74 -0
  11. tiebreak_core-1.2.0/src/tiebreak_core/fide2024.py +1215 -0
  12. tiebreak_core-1.2.0/src/tiebreak_core/fide2026.py +1586 -0
  13. tiebreak_core-1.2.0/src/tiebreak_core/models.py +149 -0
  14. tiebreak_core-1.2.0/src/tiebreak_core/modifiers.py +649 -0
  15. tiebreak_core-1.2.0/src/tiebreak_core/py.typed +0 -0
  16. tiebreak_core-1.2.0/src/tiebreak_core/ranking.py +94 -0
  17. tiebreak_core-1.2.0/src/tiebreak_core/registry.py +147 -0
  18. tiebreak_core-1.2.0/src/tiebreak_core/rules.py +170 -0
  19. tiebreak_core-1.2.0/src/tiebreak_core/scoring.py +87 -0
  20. tiebreak_core-1.2.0/src/tiebreak_core/strict.py +619 -0
  21. tiebreak_core-1.2.0/src/tiebreak_core/team.py +1070 -0
  22. tiebreak_core-1.2.0/src/tiebreak_core.egg-info/PKG-INFO +279 -0
  23. tiebreak_core-1.2.0/src/tiebreak_core.egg-info/SOURCES.txt +42 -0
  24. tiebreak_core-1.2.0/src/tiebreak_core.egg-info/dependency_links.txt +1 -0
  25. tiebreak_core-1.2.0/src/tiebreak_core.egg-info/top_level.txt +1 -0
  26. tiebreak_core-1.2.0/tests/test_benchmarks.py +162 -0
  27. tiebreak_core-1.2.0/tests/test_calculators.py +204 -0
  28. tiebreak_core-1.2.0/tests/test_conformance.py +200 -0
  29. tiebreak_core-1.2.0/tests/test_corpus.py +111 -0
  30. tiebreak_core-1.2.0/tests/test_de_order_invariance.py +143 -0
  31. tiebreak_core-1.2.0/tests/test_differential.py +241 -0
  32. tiebreak_core-1.2.0/tests/test_direct.py +228 -0
  33. tiebreak_core-1.2.0/tests/test_examples.py +33 -0
  34. tiebreak_core-1.2.0/tests/test_fide2024.py +290 -0
  35. tiebreak_core-1.2.0/tests/test_fide2026.py +1071 -0
  36. tiebreak_core-1.2.0/tests/test_gamekind.py +104 -0
  37. tiebreak_core-1.2.0/tests/test_legacy_frozen.py +137 -0
  38. tiebreak_core-1.2.0/tests/test_modifiers.py +354 -0
  39. tiebreak_core-1.2.0/tests/test_policy_scoring.py +157 -0
  40. tiebreak_core-1.2.0/tests/test_ranking.py +67 -0
  41. tiebreak_core-1.2.0/tests/test_ratings.py +122 -0
  42. tiebreak_core-1.2.0/tests/test_rulesets.py +56 -0
  43. tiebreak_core-1.2.0/tests/test_strict.py +241 -0
  44. tiebreak_core-1.2.0/tests/test_team.py +341 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 20kevit
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,279 @@
1
+ Metadata-Version: 2.4
2
+ Name: tiebreak-core
3
+ Version: 1.2.0
4
+ Summary: Standalone FIDE chess tie-break calculation library for individual and team tournaments
5
+ Author: 20kevit
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/20kevit/tiebreak-core
8
+ Project-URL: Repository, https://github.com/20kevit/tiebreak-core
9
+ Keywords: chess,tiebreak,tie-break,fide,tournament,swiss,round-robin
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Topic :: Games/Entertainment :: Board Games
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Dynamic: license-file
24
+
25
+ # tiebreak-core
26
+
27
+ [![CI](https://github.com/20kevit/tiebreak-core/actions/workflows/ci.yml/badge.svg)](https://github.com/20kevit/tiebreak-core/actions/workflows/ci.yml)
28
+
29
+ Standalone Python library for chess tie-break calculation, implementing the applicable FIDE C.07 semantics for individual and team tournaments.
30
+
31
+ Pure Python. Zero runtime dependencies. Deterministic. MIT licensed.
32
+
33
+ ## What it is
34
+
35
+ `tiebreak-core` computes chess tie-break values and orderings from normalized tournament data you supply: per-player game records (individual) or per-team match records (team). It implements the FIDE C.07 calculation semantics — Buchholz families, Sonneborn-Berger, progressive scores, Koya, rating-based criteria, Direct Encounter, team systems (MP/GP, Board Count, Extended Sonneborn-Berger, Extended Direct Encounter, SSSC), and the generic MTB26 modifier machine (`/Cn`, `/Mn`, `/L±n`, `/P`, `/F`, `/R`, `:MP/:GP`) — under explicit, versioned rulesets.
36
+
37
+ ## What it is NOT
38
+
39
+ Not a tournament manager, not a pairing engine, not a rating calculator, not a TRF file parser, not a web service, not a database layer. It owns calculation and ordering only; seeding, pairing, persistence, and file I/O belong to consumers. It is **not FIDE-approved software** — no such claim is made (see [FIDE conformity](#fide-conformity) below).
40
+
41
+ ## Why it exists
42
+
43
+ Tournament applications (`chess-manager`, `pairing-core` integrations, future tools) all need the same FIDE tie-break arithmetic. `tiebreak-core` is the shared, independently testable home for it, with frozen ruleset semantics so historical results never shift under callers.
44
+
45
+ ## Features
46
+
47
+ - **Individual Swiss / Round Robin / pre-determined pairings** — Article 16 unplayed-round management (categories, adjusted scores, capped/uncapped dummies, VUR-aware cuts), Swiss vs RR forfeit scope flag.
48
+ - **Team tournaments** — full C.07 Articles 11–13 on a dedicated `TeamMatch`/`TeamRecord` model.
49
+ - **Generic modifiers** — any valid MTB26 descriptor (`BH/C3`, `ARO/M2`, `KS/L+1`, `SB/C2/P`, `TPN/R`), with typed rejection of FIDE-undefined combinations.
50
+ - **Explicit rulesets** — `legacy-0.1.0` (frozen), `fide-2024` (frozen), `fide-2026` (current). Same input + same ruleset → same output, forever.
51
+ - **Typed failures** — `UnknownCriterionError`, `UnsupportedCriterionError`, `InvalidDescriptorError`, `InvalidGameRecordError`, `InvalidPlayerDataError`, and more. No silent fallback on the strict path.
52
+ - **Deterministic ranking** — staged ordering (points → criteria → group stages → explicit keys), no mutation of caller data.
53
+
54
+ ## FIDE scope
55
+
56
+ | Area | Status |
57
+ |---|---|
58
+ | Individual criteria, C.07 Arts. 6–10 + modifiers Art. 14 | Implemented (`fide-2024`, `fide-2026`) |
59
+ | Unplayed rounds, Art. 16 (both editions) + RR §15.2 carve-out | Implemented |
60
+ | Team criteria, Arts. 11–13 (MP/GP, BC/TBR/BBE, MPvGP, ESB×4, EDE+chains, SSSC) | Implemented |
61
+ | Rating tables (§§8.1a/8.1b), ARO/TPR/PTP/APRO/APPO/RTNG | Implemented |
62
+ | Direct Encounter (§6) / Extended DE (§13.3) as group stages | Implemented |
63
+ | Play-off formats (Art. 3), pairing-time evaluation (C.04), norm calculations (B.01) | Consumer-owned / out of scope |
64
+ | Exotic scoring tables without explicit per-round scores | Rejected with a typed error |
65
+
66
+ Details: `docs/FIDE_COMPLETE_REQUIREMENTS_MATRIX.md`. Limitations: `docs/KNOWN_LIMITATIONS.md`.
67
+
68
+ ## Rulesets
69
+
70
+ ```python
71
+ from tiebreak_core import available_rulesets
72
+ for rs in available_rulesets():
73
+ print(rs.id, "-", rs.status)
74
+ # legacy-0.1.0 - implemented (frozen behavior-preserving extraction)
75
+ # fide-2026 - implemented (current C.07: §16.4 caps, RR mode, TPN/RTNG, /P)
76
+ # fide-2024 - implemented (frozen era semantics)
77
+ ```
78
+
79
+ See `docs/VERSIONING.md` for the frozen-ruleset policy.
80
+
81
+ ## Installation
82
+
83
+ Requires Python ≥ 3.10. No dependencies.
84
+
85
+ ```bash
86
+ pip install tiebreak-core
87
+ ```
88
+
89
+ From source:
90
+
91
+ ```bash
92
+ pip install -e .
93
+ ```
94
+
95
+ ## Quick start — individual Swiss
96
+
97
+ ```python
98
+ from tiebreak_core import (
99
+ GameRecord, PlayerTiebreakData,
100
+ calculate_all_strict, rank_standings_strict,
101
+ )
102
+
103
+ players = {
104
+ 1: PlayerTiebreakData(1, 2000, 2.5, [
105
+ GameRecord(2, 1900, 1.0, "white", 1, kind="played"),
106
+ GameRecord(3, 1950, 0.5, "black", 2, kind="played"),
107
+ GameRecord(4, 1800, 1.0, "white", 3, kind="played"),
108
+ ]),
109
+ 2: PlayerTiebreakData(2, 1900, 1.0, [
110
+ GameRecord(1, 2000, 0.0, "black", 1, kind="played"),
111
+ GameRecord(4, 1800, 1.0, "white", 2, kind="played"),
112
+ GameRecord(3, 1950, 0.0, "black", 3, kind="played"),
113
+ ]),
114
+ 3: PlayerTiebreakData(3, 1950, 2.0, [
115
+ GameRecord(4, 1800, 1.0, "white", 1, kind="played"),
116
+ GameRecord(1, 2000, 0.5, "white", 2, kind="played"),
117
+ GameRecord(2, 1900, 0.5, "white", 3, kind="played"),
118
+ ]),
119
+ 4: PlayerTiebreakData(4, 1800, 0.0, [
120
+ GameRecord(3, 1950, 0.0, "black", 1, kind="played"),
121
+ GameRecord(2, 1900, 0.0, "black", 2, kind="played"),
122
+ GameRecord(1, 2000, 0.0, "black", 3, kind="played"),
123
+ ]),
124
+ }
125
+ criteria = ["buchholz_cut1", "sonneborn_berger"]
126
+
127
+ values = calculate_all_strict(players[1], players, criteria,
128
+ total_rounds=3, ruleset="fide-2026")
129
+ # {'buchholz_cut1': 3.0, 'sonneborn_berger': 2.0}
130
+
131
+ standings = rank_standings_strict(players, criteria, total_rounds=3,
132
+ ruleset="fide-2026")
133
+ print([p.player_id for p in standings.players]) # [1, 3, 2, 4]
134
+ ```
135
+
136
+ Game kinds (`played`, `pairing_bye`, `forfeit_win`, `forfeit_loss`, `requested_bye`, …) drive Article 16 handling — see `docs/FIDE_DATA_SEMANTICS.md`. More: `examples/a_individual_swiss.py`, `examples/b_individual_round_robin.py`.
137
+
138
+ ## Team tournaments
139
+
140
+ ```python
141
+ from tiebreak_core import (
142
+ TeamFormat, TeamMatch, TeamRecord,
143
+ calculate_team_strict, rank_teams_strict,
144
+ )
145
+
146
+ fmt = TeamFormat(mp_win=2.0, mp_draw=1.0)
147
+ teams = {
148
+ 1: TeamRecord(1, 3.0, 5.0, [
149
+ TeamMatch(2, 1, 2, 3.0),
150
+ TeamMatch(2, 2, 1, 2.0)], (2.5, 1.5, 0.5, 0.5)),
151
+ 2: TeamRecord(2, 1.0, 3.0, [
152
+ TeamMatch(1, 1, 0, 1.0),
153
+ TeamMatch(1, 2, 1, 2.0)], (1.5, 0.5, 0.5, 0.5)),
154
+ }
155
+
156
+ emmsb = calculate_team_strict(teams[1], teams, "EMMSB",
157
+ total_rounds=2, fmt=fmt) # 3.0
158
+ standings = rank_teams_strict(teams, ["EMMSB", "EDE", "BC"],
159
+ total_rounds=2, fmt=fmt)
160
+ ```
161
+
162
+ Board totals (`board_points`) back the §12 codes; forfeit/PAB legs are recorded at standard-win values per the §12 intro. More: `examples/c_team_tournament.py`.
163
+
164
+ ## Generic modifiers
165
+
166
+ ```python
167
+ from tiebreak_core import (
168
+ calculate_descriptor_strict, rank_descriptors_strict, parse_descriptor,
169
+ )
170
+
171
+ parse_descriptor("SB/C2/P") # base SB + Cut-2 + forfeit inclusion
172
+ calculate_descriptor_strict(player, players, "BH/C3", total_rounds=9,
173
+ ruleset="fide-2026")
174
+ rank_descriptors_strict(players, ["BH/C1", "SB/C1", "DE", "TPN/R"],
175
+ total_rounds=9, ruleset="fide-2026",
176
+ pairing_numbers={...})
177
+ ```
178
+
179
+ `n=1/2` resolve to the named implementations (equivalence-tested); arbitrary valid `n`, `/L±n` Koya limits, and `/R` terminal reversal work uniformly. Invalid combinations raise `InvalidDescriptorError`. More: `examples/d_generic_modifier.py`, `examples/e_invalid_modifier.py`.
180
+
181
+ ## Ruleset selection and Article 16 policy
182
+
183
+ Pass `ruleset=` explicitly on the strict path (`legacy-0.1.0`, `fide-2024`, `fide-2026`); `mode="round_robin"` selects the §15.2 regime under `fide-2026`. Unplayed-round policy is inspectable as data:
184
+
185
+ ```python
186
+ from tiebreak_core import resolve_policy
187
+ resolve_policy("fide-2026") # capped 2026 dummies
188
+ resolve_policy("fide-2026", mode="round_robin") # §15.2 forfeit scope
189
+ resolve_policy("fide-2026", late_bye_value=0.0) # explicit §16.6 override
190
+ ```
191
+
192
+ More: `examples/f_article16_policy.py`, `examples/g_deterministic_ranking.py`.
193
+
194
+ ## Error handling
195
+
196
+ Every failure is typed and actionable: unknown criteria (`UnknownCriterionError`), unimplemented combinations (`UnsupportedCriterionError`), malformed descriptors (`InvalidDescriptorError`), bad records (`InvalidGameRecordError` / `InvalidPlayerDataError`), bad rulesets (`UnsupportedRulesetError`), registry misuse (`RegistryError`). The legacy path preserves its frozen lenient behavior; new code should use the strict path.
197
+
198
+ ## Determinism and correctness
199
+
200
+ Same input + same ruleset → same output. Calculations never mutate caller data. Ranking sorts exact internal values (presentation rounding never decides order). The suite covers official FIDE worked values, definition-derived hand calculations, differential comparison against an independent engine, property/metamorphic invariants, invalid-input matrices, and benchmarks.
201
+
202
+ ## Validation and evidence
203
+
204
+ - `tests/corpus/` — official FIDE worked examples (each case names its source).
205
+ - `tests/test_differential.py` — independent-oracle comparison harness.
206
+ - `tests/test_examples.py` — every `examples/*.py` script executes in CI.
207
+ - Evidence classes (`PRIMARY_NORMATIVE`, `OFFICIAL_VALUE`, `INDEPENDENT_ORACLE`, `PROJECT_DERIVED`) are defined in `docs/FIDE_SOURCE_REGISTRY.md` and never mixed.
208
+
209
+ ## Architecture
210
+
211
+ ```
212
+ consumer (chess-manager, pairing-core callers, your app)
213
+ │ plain dataclasses in, immutable results out
214
+ ▼
215
+ tiebreak_core
216
+ modifiers → MTB26 descriptor parsing + generic cut/median engine
217
+ fide2026 / fide2024 → individual calculation engines (versioned)
218
+ team → team domain (§§11–13) on TeamMatch records
219
+ ranking → pure staged ordering (scalar / group / terminal stages)
220
+ strict → validated boundary with typed errors
221
+ article16 / scoring → policy + point-table models
222
+ registry / rules / errors / models / display
223
+ ```
224
+
225
+ Details: `docs/ARCHITECTURE.md`, `docs/DOMAIN_MODEL.md`, `docs/adr/`.
226
+
227
+ ## Package / API overview
228
+
229
+ Top-level exports (~100 names): input models (`GameRecord`, `PlayerTiebreakData`, `TeamMatch`, `TeamRecord`, `TeamFormat`), calculators (`calculate*`, `calculate_descriptor*`, `calculate_team*`), ranking (`rank_standings*`, `rank_descriptors*`, `rank_team_standings*`, `order_ids*`), rulesets (`available_rulesets`, `describe_ruleset`), policy/scoring (`resolve_policy`, `ScoringScheme`), and all error types. Full surface: `docs/API.md`.
230
+
231
+ ## Documentation map
232
+
233
+ | Document | Purpose |
234
+ |---|---|
235
+ | `docs/API.md` | Complete API reference with examples |
236
+ | `docs/ARCHITECTURE.md` | Boundaries, layers, dependency direction |
237
+ | `docs/FIDE_CRITERIA_CATALOG.md` | Per-criterion formulas and status |
238
+ | `docs/FIDE_TIEBREAK_MODIFIERS.md` | Modifier inventory (`/Cn /Mn /L /P /F /R /Kx / :MP/:GP`) |
239
+ | `docs/FIDE_MTB26_CATALOG.md` | MTB26 descriptor table + core-request boundary |
240
+ | `docs/FIDE_SOURCE_REGISTRY.md` | Auditable source index with evidence classes |
241
+ | `docs/FIDE_2026_DIFF.md` | 2024→2026 word-level diff (D1–D15) |
242
+ | `docs/KNOWN_LIMITATIONS.md` | Explicit scope boundaries |
243
+ | `docs/VERSIONING.md` | Ruleset vs package versioning policy |
244
+ | `docs/DEVELOPMENT.md` | Development and testing guide |
245
+ | `CHANGELOG.md` | Release notes |
246
+
247
+ ## FIDE references
248
+
249
+ Normative basis: FIDE Handbook C.07 Play-Off and Tie-Break Regulations (editions effective 1 Aug 2024 and 1 Mar 2026), the MTB26 mandatory tie-break table, FIDE Rating Regulations §§8.1a/8.1b, and FIDE TEC worked examples. Full index: `docs/FIDE_SOURCE_REGISTRY.md`.
250
+
251
+ ## FIDE conformity
252
+
253
+ This library **implements the applicable FIDE C.07 calculation semantics** within its documented scope and is **validated against authoritative FIDE material** (official worked values, definition-derived tests, independent differential checks). It is **not FIDE-approved, certified, or endorsed** — approval attaches to complete tournament-helper programs through FIDE's own process (see `docs/FIDE_APPROVAL_PATH.md`), never to a calculation library alone.
254
+
255
+ ## Known limitations (summary)
256
+
257
+ Exotic scoring tables require explicit per-round opponent scores (typed error otherwise); the Buchholz round-robin restriction is documented, not enforced (organizer list duty); team edge readings without retrieved official examples are documented `PROJECT_DERIVED` interpretations; rating inputs must be the tournament-start snapshot (consumer contract). Full list: `docs/KNOWN_LIMITATIONS.md`.
258
+
259
+ ## Development and testing
260
+
261
+ ```bash
262
+ pip install -e .
263
+ python -m pytest tests -q # full suite
264
+ python -m pytest tests/test_examples.py -q # examples gate
265
+ ```
266
+
267
+ See `docs/DEVELOPMENT.md`. CI runs Python 3.10–3.12 plus a packaging smoke test.
268
+
269
+ ## Contributing
270
+
271
+ Issues and pull requests are welcome. Preserve frozen-ruleset outputs (see `docs/VERSIONING.md`), keep the dependency footprint at zero, add regression coverage for behavior changes, and never claim FIDE approval.
272
+
273
+ ## License
274
+
275
+ MIT — see `LICENSE`.
276
+
277
+ ## Status
278
+
279
+ Current release: **1.2.0** (`docs/VERSIONING.md`, `CHANGELOG.md`). Stable public API; additive extensions only — output-changing corrections to published rulesets require new ruleset ids (sole pre-publication exception: the F4 DE mini-table correction, documented in `CHANGELOG.md`).
@@ -0,0 +1,255 @@
1
+ # tiebreak-core
2
+
3
+ [![CI](https://github.com/20kevit/tiebreak-core/actions/workflows/ci.yml/badge.svg)](https://github.com/20kevit/tiebreak-core/actions/workflows/ci.yml)
4
+
5
+ Standalone Python library for chess tie-break calculation, implementing the applicable FIDE C.07 semantics for individual and team tournaments.
6
+
7
+ Pure Python. Zero runtime dependencies. Deterministic. MIT licensed.
8
+
9
+ ## What it is
10
+
11
+ `tiebreak-core` computes chess tie-break values and orderings from normalized tournament data you supply: per-player game records (individual) or per-team match records (team). It implements the FIDE C.07 calculation semantics — Buchholz families, Sonneborn-Berger, progressive scores, Koya, rating-based criteria, Direct Encounter, team systems (MP/GP, Board Count, Extended Sonneborn-Berger, Extended Direct Encounter, SSSC), and the generic MTB26 modifier machine (`/Cn`, `/Mn`, `/L±n`, `/P`, `/F`, `/R`, `:MP/:GP`) — under explicit, versioned rulesets.
12
+
13
+ ## What it is NOT
14
+
15
+ Not a tournament manager, not a pairing engine, not a rating calculator, not a TRF file parser, not a web service, not a database layer. It owns calculation and ordering only; seeding, pairing, persistence, and file I/O belong to consumers. It is **not FIDE-approved software** — no such claim is made (see [FIDE conformity](#fide-conformity) below).
16
+
17
+ ## Why it exists
18
+
19
+ Tournament applications (`chess-manager`, `pairing-core` integrations, future tools) all need the same FIDE tie-break arithmetic. `tiebreak-core` is the shared, independently testable home for it, with frozen ruleset semantics so historical results never shift under callers.
20
+
21
+ ## Features
22
+
23
+ - **Individual Swiss / Round Robin / pre-determined pairings** — Article 16 unplayed-round management (categories, adjusted scores, capped/uncapped dummies, VUR-aware cuts), Swiss vs RR forfeit scope flag.
24
+ - **Team tournaments** — full C.07 Articles 11–13 on a dedicated `TeamMatch`/`TeamRecord` model.
25
+ - **Generic modifiers** — any valid MTB26 descriptor (`BH/C3`, `ARO/M2`, `KS/L+1`, `SB/C2/P`, `TPN/R`), with typed rejection of FIDE-undefined combinations.
26
+ - **Explicit rulesets** — `legacy-0.1.0` (frozen), `fide-2024` (frozen), `fide-2026` (current). Same input + same ruleset → same output, forever.
27
+ - **Typed failures** — `UnknownCriterionError`, `UnsupportedCriterionError`, `InvalidDescriptorError`, `InvalidGameRecordError`, `InvalidPlayerDataError`, and more. No silent fallback on the strict path.
28
+ - **Deterministic ranking** — staged ordering (points → criteria → group stages → explicit keys), no mutation of caller data.
29
+
30
+ ## FIDE scope
31
+
32
+ | Area | Status |
33
+ |---|---|
34
+ | Individual criteria, C.07 Arts. 6–10 + modifiers Art. 14 | Implemented (`fide-2024`, `fide-2026`) |
35
+ | Unplayed rounds, Art. 16 (both editions) + RR §15.2 carve-out | Implemented |
36
+ | Team criteria, Arts. 11–13 (MP/GP, BC/TBR/BBE, MPvGP, ESB×4, EDE+chains, SSSC) | Implemented |
37
+ | Rating tables (§§8.1a/8.1b), ARO/TPR/PTP/APRO/APPO/RTNG | Implemented |
38
+ | Direct Encounter (§6) / Extended DE (§13.3) as group stages | Implemented |
39
+ | Play-off formats (Art. 3), pairing-time evaluation (C.04), norm calculations (B.01) | Consumer-owned / out of scope |
40
+ | Exotic scoring tables without explicit per-round scores | Rejected with a typed error |
41
+
42
+ Details: `docs/FIDE_COMPLETE_REQUIREMENTS_MATRIX.md`. Limitations: `docs/KNOWN_LIMITATIONS.md`.
43
+
44
+ ## Rulesets
45
+
46
+ ```python
47
+ from tiebreak_core import available_rulesets
48
+ for rs in available_rulesets():
49
+ print(rs.id, "-", rs.status)
50
+ # legacy-0.1.0 - implemented (frozen behavior-preserving extraction)
51
+ # fide-2026 - implemented (current C.07: §16.4 caps, RR mode, TPN/RTNG, /P)
52
+ # fide-2024 - implemented (frozen era semantics)
53
+ ```
54
+
55
+ See `docs/VERSIONING.md` for the frozen-ruleset policy.
56
+
57
+ ## Installation
58
+
59
+ Requires Python ≥ 3.10. No dependencies.
60
+
61
+ ```bash
62
+ pip install tiebreak-core
63
+ ```
64
+
65
+ From source:
66
+
67
+ ```bash
68
+ pip install -e .
69
+ ```
70
+
71
+ ## Quick start — individual Swiss
72
+
73
+ ```python
74
+ from tiebreak_core import (
75
+ GameRecord, PlayerTiebreakData,
76
+ calculate_all_strict, rank_standings_strict,
77
+ )
78
+
79
+ players = {
80
+ 1: PlayerTiebreakData(1, 2000, 2.5, [
81
+ GameRecord(2, 1900, 1.0, "white", 1, kind="played"),
82
+ GameRecord(3, 1950, 0.5, "black", 2, kind="played"),
83
+ GameRecord(4, 1800, 1.0, "white", 3, kind="played"),
84
+ ]),
85
+ 2: PlayerTiebreakData(2, 1900, 1.0, [
86
+ GameRecord(1, 2000, 0.0, "black", 1, kind="played"),
87
+ GameRecord(4, 1800, 1.0, "white", 2, kind="played"),
88
+ GameRecord(3, 1950, 0.0, "black", 3, kind="played"),
89
+ ]),
90
+ 3: PlayerTiebreakData(3, 1950, 2.0, [
91
+ GameRecord(4, 1800, 1.0, "white", 1, kind="played"),
92
+ GameRecord(1, 2000, 0.5, "white", 2, kind="played"),
93
+ GameRecord(2, 1900, 0.5, "white", 3, kind="played"),
94
+ ]),
95
+ 4: PlayerTiebreakData(4, 1800, 0.0, [
96
+ GameRecord(3, 1950, 0.0, "black", 1, kind="played"),
97
+ GameRecord(2, 1900, 0.0, "black", 2, kind="played"),
98
+ GameRecord(1, 2000, 0.0, "black", 3, kind="played"),
99
+ ]),
100
+ }
101
+ criteria = ["buchholz_cut1", "sonneborn_berger"]
102
+
103
+ values = calculate_all_strict(players[1], players, criteria,
104
+ total_rounds=3, ruleset="fide-2026")
105
+ # {'buchholz_cut1': 3.0, 'sonneborn_berger': 2.0}
106
+
107
+ standings = rank_standings_strict(players, criteria, total_rounds=3,
108
+ ruleset="fide-2026")
109
+ print([p.player_id for p in standings.players]) # [1, 3, 2, 4]
110
+ ```
111
+
112
+ Game kinds (`played`, `pairing_bye`, `forfeit_win`, `forfeit_loss`, `requested_bye`, …) drive Article 16 handling — see `docs/FIDE_DATA_SEMANTICS.md`. More: `examples/a_individual_swiss.py`, `examples/b_individual_round_robin.py`.
113
+
114
+ ## Team tournaments
115
+
116
+ ```python
117
+ from tiebreak_core import (
118
+ TeamFormat, TeamMatch, TeamRecord,
119
+ calculate_team_strict, rank_teams_strict,
120
+ )
121
+
122
+ fmt = TeamFormat(mp_win=2.0, mp_draw=1.0)
123
+ teams = {
124
+ 1: TeamRecord(1, 3.0, 5.0, [
125
+ TeamMatch(2, 1, 2, 3.0),
126
+ TeamMatch(2, 2, 1, 2.0)], (2.5, 1.5, 0.5, 0.5)),
127
+ 2: TeamRecord(2, 1.0, 3.0, [
128
+ TeamMatch(1, 1, 0, 1.0),
129
+ TeamMatch(1, 2, 1, 2.0)], (1.5, 0.5, 0.5, 0.5)),
130
+ }
131
+
132
+ emmsb = calculate_team_strict(teams[1], teams, "EMMSB",
133
+ total_rounds=2, fmt=fmt) # 3.0
134
+ standings = rank_teams_strict(teams, ["EMMSB", "EDE", "BC"],
135
+ total_rounds=2, fmt=fmt)
136
+ ```
137
+
138
+ Board totals (`board_points`) back the §12 codes; forfeit/PAB legs are recorded at standard-win values per the §12 intro. More: `examples/c_team_tournament.py`.
139
+
140
+ ## Generic modifiers
141
+
142
+ ```python
143
+ from tiebreak_core import (
144
+ calculate_descriptor_strict, rank_descriptors_strict, parse_descriptor,
145
+ )
146
+
147
+ parse_descriptor("SB/C2/P") # base SB + Cut-2 + forfeit inclusion
148
+ calculate_descriptor_strict(player, players, "BH/C3", total_rounds=9,
149
+ ruleset="fide-2026")
150
+ rank_descriptors_strict(players, ["BH/C1", "SB/C1", "DE", "TPN/R"],
151
+ total_rounds=9, ruleset="fide-2026",
152
+ pairing_numbers={...})
153
+ ```
154
+
155
+ `n=1/2` resolve to the named implementations (equivalence-tested); arbitrary valid `n`, `/L±n` Koya limits, and `/R` terminal reversal work uniformly. Invalid combinations raise `InvalidDescriptorError`. More: `examples/d_generic_modifier.py`, `examples/e_invalid_modifier.py`.
156
+
157
+ ## Ruleset selection and Article 16 policy
158
+
159
+ Pass `ruleset=` explicitly on the strict path (`legacy-0.1.0`, `fide-2024`, `fide-2026`); `mode="round_robin"` selects the §15.2 regime under `fide-2026`. Unplayed-round policy is inspectable as data:
160
+
161
+ ```python
162
+ from tiebreak_core import resolve_policy
163
+ resolve_policy("fide-2026") # capped 2026 dummies
164
+ resolve_policy("fide-2026", mode="round_robin") # §15.2 forfeit scope
165
+ resolve_policy("fide-2026", late_bye_value=0.0) # explicit §16.6 override
166
+ ```
167
+
168
+ More: `examples/f_article16_policy.py`, `examples/g_deterministic_ranking.py`.
169
+
170
+ ## Error handling
171
+
172
+ Every failure is typed and actionable: unknown criteria (`UnknownCriterionError`), unimplemented combinations (`UnsupportedCriterionError`), malformed descriptors (`InvalidDescriptorError`), bad records (`InvalidGameRecordError` / `InvalidPlayerDataError`), bad rulesets (`UnsupportedRulesetError`), registry misuse (`RegistryError`). The legacy path preserves its frozen lenient behavior; new code should use the strict path.
173
+
174
+ ## Determinism and correctness
175
+
176
+ Same input + same ruleset → same output. Calculations never mutate caller data. Ranking sorts exact internal values (presentation rounding never decides order). The suite covers official FIDE worked values, definition-derived hand calculations, differential comparison against an independent engine, property/metamorphic invariants, invalid-input matrices, and benchmarks.
177
+
178
+ ## Validation and evidence
179
+
180
+ - `tests/corpus/` — official FIDE worked examples (each case names its source).
181
+ - `tests/test_differential.py` — independent-oracle comparison harness.
182
+ - `tests/test_examples.py` — every `examples/*.py` script executes in CI.
183
+ - Evidence classes (`PRIMARY_NORMATIVE`, `OFFICIAL_VALUE`, `INDEPENDENT_ORACLE`, `PROJECT_DERIVED`) are defined in `docs/FIDE_SOURCE_REGISTRY.md` and never mixed.
184
+
185
+ ## Architecture
186
+
187
+ ```
188
+ consumer (chess-manager, pairing-core callers, your app)
189
+ │ plain dataclasses in, immutable results out
190
+ ▼
191
+ tiebreak_core
192
+ modifiers → MTB26 descriptor parsing + generic cut/median engine
193
+ fide2026 / fide2024 → individual calculation engines (versioned)
194
+ team → team domain (§§11–13) on TeamMatch records
195
+ ranking → pure staged ordering (scalar / group / terminal stages)
196
+ strict → validated boundary with typed errors
197
+ article16 / scoring → policy + point-table models
198
+ registry / rules / errors / models / display
199
+ ```
200
+
201
+ Details: `docs/ARCHITECTURE.md`, `docs/DOMAIN_MODEL.md`, `docs/adr/`.
202
+
203
+ ## Package / API overview
204
+
205
+ Top-level exports (~100 names): input models (`GameRecord`, `PlayerTiebreakData`, `TeamMatch`, `TeamRecord`, `TeamFormat`), calculators (`calculate*`, `calculate_descriptor*`, `calculate_team*`), ranking (`rank_standings*`, `rank_descriptors*`, `rank_team_standings*`, `order_ids*`), rulesets (`available_rulesets`, `describe_ruleset`), policy/scoring (`resolve_policy`, `ScoringScheme`), and all error types. Full surface: `docs/API.md`.
206
+
207
+ ## Documentation map
208
+
209
+ | Document | Purpose |
210
+ |---|---|
211
+ | `docs/API.md` | Complete API reference with examples |
212
+ | `docs/ARCHITECTURE.md` | Boundaries, layers, dependency direction |
213
+ | `docs/FIDE_CRITERIA_CATALOG.md` | Per-criterion formulas and status |
214
+ | `docs/FIDE_TIEBREAK_MODIFIERS.md` | Modifier inventory (`/Cn /Mn /L /P /F /R /Kx / :MP/:GP`) |
215
+ | `docs/FIDE_MTB26_CATALOG.md` | MTB26 descriptor table + core-request boundary |
216
+ | `docs/FIDE_SOURCE_REGISTRY.md` | Auditable source index with evidence classes |
217
+ | `docs/FIDE_2026_DIFF.md` | 2024→2026 word-level diff (D1–D15) |
218
+ | `docs/KNOWN_LIMITATIONS.md` | Explicit scope boundaries |
219
+ | `docs/VERSIONING.md` | Ruleset vs package versioning policy |
220
+ | `docs/DEVELOPMENT.md` | Development and testing guide |
221
+ | `CHANGELOG.md` | Release notes |
222
+
223
+ ## FIDE references
224
+
225
+ Normative basis: FIDE Handbook C.07 Play-Off and Tie-Break Regulations (editions effective 1 Aug 2024 and 1 Mar 2026), the MTB26 mandatory tie-break table, FIDE Rating Regulations §§8.1a/8.1b, and FIDE TEC worked examples. Full index: `docs/FIDE_SOURCE_REGISTRY.md`.
226
+
227
+ ## FIDE conformity
228
+
229
+ This library **implements the applicable FIDE C.07 calculation semantics** within its documented scope and is **validated against authoritative FIDE material** (official worked values, definition-derived tests, independent differential checks). It is **not FIDE-approved, certified, or endorsed** — approval attaches to complete tournament-helper programs through FIDE's own process (see `docs/FIDE_APPROVAL_PATH.md`), never to a calculation library alone.
230
+
231
+ ## Known limitations (summary)
232
+
233
+ Exotic scoring tables require explicit per-round opponent scores (typed error otherwise); the Buchholz round-robin restriction is documented, not enforced (organizer list duty); team edge readings without retrieved official examples are documented `PROJECT_DERIVED` interpretations; rating inputs must be the tournament-start snapshot (consumer contract). Full list: `docs/KNOWN_LIMITATIONS.md`.
234
+
235
+ ## Development and testing
236
+
237
+ ```bash
238
+ pip install -e .
239
+ python -m pytest tests -q # full suite
240
+ python -m pytest tests/test_examples.py -q # examples gate
241
+ ```
242
+
243
+ See `docs/DEVELOPMENT.md`. CI runs Python 3.10–3.12 plus a packaging smoke test.
244
+
245
+ ## Contributing
246
+
247
+ Issues and pull requests are welcome. Preserve frozen-ruleset outputs (see `docs/VERSIONING.md`), keep the dependency footprint at zero, add regression coverage for behavior changes, and never claim FIDE approval.
248
+
249
+ ## License
250
+
251
+ MIT — see `LICENSE`.
252
+
253
+ ## Status
254
+
255
+ Current release: **1.2.0** (`docs/VERSIONING.md`, `CHANGELOG.md`). Stable public API; additive extensions only — output-changing corrections to published rulesets require new ruleset ids (sole pre-publication exception: the F4 DE mini-table correction, documented in `CHANGELOG.md`).
@@ -0,0 +1,39 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "tiebreak-core"
7
+ version = "1.2.0"
8
+ description = "Standalone FIDE chess tie-break calculation library for individual and team tournaments"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "20kevit" }]
13
+ keywords = ["chess", "tiebreak", "tie-break", "fide", "tournament", "swiss", "round-robin"]
14
+ classifiers = [
15
+ "Development Status :: 5 - Production/Stable",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3 :: Only",
23
+ "Topic :: Games/Entertainment :: Board Games",
24
+ "Typing :: Typed",
25
+ ]
26
+ dependencies = []
27
+
28
+ [project.urls]
29
+ Homepage = "https://github.com/20kevit/tiebreak-core"
30
+ Repository = "https://github.com/20kevit/tiebreak-core"
31
+
32
+ [tool.setuptools.packages.find]
33
+ where = ["src"]
34
+
35
+ [tool.setuptools.package-data]
36
+ tiebreak_core = ["py.typed"]
37
+
38
+ [tool.pytest.ini_options]
39
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+