OCN — Open Chess Naming
OCN is a hierarchical, human-readable naming scheme for chess openings, designed as a companion to ECO (the Encyclopaedia of Chess Openings code system that has been the de facto standard since 1971). OCN keeps the best idea in ECO — the five structural families A/B/C/D/E — and replaces the arbitrary 100-codes-per-letter sub-numbering with short, parent-aware slugs.
At a glance
| ECO | OCN-1 | Canonical name |
|---|---|---|
B33 |
B.Sic.Sve |
Sicilian Sveshnikov |
B90 |
B.Sic.Naj.Eng |
Najdorf English Attack |
C67 |
C.RyL.Ber.Wal.End |
Ruy López, Berlin Wall Endgame |
D47 |
D.Sem.Mer |
Semi-Slav, Meran |
E97 |
E.KID.Cls.Mar |
KID Classical, Mar del Plata |
Read once, remember forever. No lookup table needed.
Five-minute quickstart
pip install . # from a checkout today
pip install ocn-chess # from PyPI, with the next tagged release
The catalogue travels inside the package: no download, no database, no third-party dependency, and the lookups work on a plane.
from ocn import Catalog
cat = Catalog.load() # the bundled catalogue, offline
cat.by_slug("B.Sic.Naj.Eng").canonical_name # -> 'Sicilian Najdorf, English Attack'
[row.ocn1 for row in cat.parents("B.Sic.Naj.Eng")] # -> ['B', 'B.Sic', 'B.Sic.Naj']
len(cat.by_eco("B90")) # -> 20
fen = "rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq c6 0 2" # 1.e4 c5
cat.by_fen(fen)[0].ocn1 # -> 'B.Sic'
Twenty OCN rows share the single ECO code B90, and the breadcrumb
reads the hierarchy back out — that is the whole pitch, in four lines.
The FEN above ends in an en-passant square no pawn can legally capture
on, which is what most board libraries print and what silently returns
zero rows everywhere else; by_fen normalises it (see
ocn/fen.py).
Then name your own games:
ocn annotate games.pgn --stats > named.pgn
Every game gains [OCN "..."] and [OCNName "..."] headers, matched by
position at every ply — so a Najdorf reached through 1.Nf3 is named a
Najdorf — and --stats prints the coverage summary to stderr. Nothing
else in the file is touched.
The Python block above is executed and checked against its own # ->
comments by tests/test_quickstart.py on
every push: copy-paste works verbatim, or CI goes red.
Why
ECO has aged remarkably well in one respect: A/B/C/D/E captures the
fundamental structural divide of chess openings (semi-open vs open vs
closed vs Indian vs flank) and any new opening fits cleanly into one of
those five families. OCN keeps that idea. It does not keep every one of
ECO's letter assignments. 770 rows — 13.8% of the rows that carry an ECO
code — sit in an OCN class that is not among their own ECO letters, always
for a structural reason: the French is B, the London and Colle systems
are A, the Grünfeld and Benoni are E. The complete machine-readable
list is catalog/ocn-1.eco-divergence.tsv
(derived, validator-enforced); the main cases are argued under
"Borderline classifications" below and in the spec's Borderline rules.
ECO has aged poorly in another respect: the 00-99 sub-codes within each letter were assigned in 1971 according to what was fashionable at the time, distribute coverage unevenly (the Sicilian alone gets 80 codes, the irregulars share 19), and offer no parent-child hierarchy. Knowing that "B33 = Sveshnikov" or "C67 = Berlin Endgame" is a feat of memorisation that benefits no one — neither the player who wants to study an opening, nor the database designer running queries.
OCN replaces that 50-year-old sub-numbering with hierarchical slugs:
B ← top level (structural family)
B.Sic ← family (Sicilian)
B.Sic.Naj ← variation (Najdorf)
B.Sic.Naj.Eng ← sub-line (English Attack, 6.Be3)
For specific named tabiyas the slug may carry trailing SAN moves
(B.Sic.Sve.Nd5 for the 11.Nd5 main line). See the spec for the
full grammar; for everyday use the four levels above are enough.
You can read the slug at any depth and immediately know:
- The structural class of the position (
B= semi-open). - The family (
Sic= Sicilian). - The variation (
Naj= Najdorf). - And, if you want the precision, the exact tabiya down to the signature SAN move that defines it.
Format
<class> ( "." <named> )+ ( "." <move> )*
class: 1 char fromA B C D E(ECO's five families; see "Borderline classifications" for where OCN's letter differs from ECO's).named: one or more 3-char TitleCase tokens — family (Sic,RyL,KID), variation (Naj), subline (Eng) and deeper levels.move: zero or more trailing SAN-style segments, capitalised pieces, check/mate stripped (Be3,e5,Bxf6,O-O).- Separator: dot
.. - Maximum depth: 7 segments total (6 dots) — a hard cap; deeper theory is identified by position, not by longer slugs.
The full specification is in spec/OCN-1.md.
Status
Released — ocn-1.2.0 (2026-06-11). The format is stable; the reference
catalogue has 5,899 entries, every duplicate-FEN group is resolved
(unresolved_groups=0), canonical names carry their true diacritics, the
ECO legacy codes are audited, and CI runs strict legal-move/SAN validation
plus the full tool test suite under an unconditional gate. Release notes:
docs/release-ocn-1.2.0-notes.md. Post-1.2
work adds internationalised alias sidecars and consumer tooling, planned in
docs/traction-roadmap.md. Comments,
corrections and additions welcome via issues.
Naming history: previously drafted as OCS — Open Chess Slug during alpha. Renamed to OCN before public release because "slug" carries different connotations outside web-development circles. The format itself is unchanged.
Catalogue
The reference catalogue lives in catalog/ocn-1.csv
— 14 columns per row:
ocn1— the OCN-1 string (primary key)canonical_name— the canonical English nameeco_legacy— the ECO codes it coversparent_ocn1— the parent slug (nominal hierarchy)moves_uci— the defining move sequence in UCI notationdepth— the depth in the hierarchyaliases— known aliases (Sveshnikov a.k.a. Lasker–Pelikan)flags— tags from the closed setgambit,sharp,closed,endgame,theoretical,deprecatednotes— free-text notes for borderline classificationsattributed_to,attribution_source,historical_notes— sourced naming attributions (who an opening is named for, with the citation; every non-emptyattributed_tomust carry a source)transposes_to,same_as— position-identity relations (see "Three relations per slug" below)
The catalogue is licensed under CC-BY-4.0: you may use, share and adapt it for any purpose, including commercial, provided you cite "Club d'Escacs Figueres" and link to this repository.
Tools
Everything is Python 3 standard library — no third-party dependency, no
build step (chess legality is checked by the in-repo move generator,
tools/chess_uci.py). Recipes
and join patterns for consumers are in
docs/consuming-ocn.md.
The ocn-chess package
The installable form of everything below, with the catalogue bundled
inside the wheel — no checkout, no network, no dependency. Built here
under src/ocn/; pip install . from a checkout today,
PyPI with the next tagged release.
from ocn import Catalog
cat = Catalog.load()
cat.by_slug("B.Sic.Naj.Eng").canonical_name # 'Sicilian Najdorf, English Attack'
cat.by_eco("B90") # deepest first
cat.by_name("Grunfeld") # case- and diacritic-folded
cat.parents("B.Sic.Naj.Eng") # breadcrumb, root to parent
cat.by_fen(fen) # O(1); en-passant trap handled
ocn lookup B90
ocn lookup B.Sic.Naj
ocn fen "rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq c6 0 2"
ocn uci "e2e4 c7c5 g1f3 d7d6"
ocn annotate games.pgn --stats > named.pgn
annotate reads a multi-game PGN (- for stdin), tags every game with
[OCN] and [OCNName], and leaves the rest of the file byte for byte
as it found it. The match is by position at every ply, deepest hit
wins, so transpositions land on the same name as the move order they
transpose into, and transposes_to is resolved to the canonical slug.
--stats prints games, match rate, median depth in plies and the top
openings to stderr; a thousand games annotate in well under a second.
Every subcommand takes --json. Details and join patterns:
docs/consuming-ocn.md.
Consumer tools
tools/ocn.py— the in-repo reader: load the catalogue, look up a slug or a FEN, walk parents and children, resolvetransposes_toandsame_as. Use the package above unless you want a checkout-only script.tools/from_uci.py— a legal UCI move sequence in, the deepest OCN-1 row whose moves are a prefix of it out (TSV, or--json).tools/from_eco.py— an ECO code, a PGN file, or inline PGN with an[ECO "..."]tag in, the unique deepest match out;--alllists candidates for ambiguous codes.tools/from_position.py— a FEN in, matching rows out; board, side to move, castling and en passant are matched, the move counters ignored.tools/coverage_stat.py— runs theocn annotatematcher over a PGN corpus and reports only the numbers: match rate, median depth, the depth table and the top openings, as text or JSON. The reproducible script behind any published "OCN names X% of real games" figure; it streams, so a compressed dump can be piped straight into it.tools/export_positions.py— writes the derived position-indexed TSV/JSON view:fen_key, a completefenwith true halfmove/fullmove counters, transposition group size, SAN movetext, EPD and the Polyglotzobrist, all computed here in Python (roadmap H2.8). This is the index the package bundles.tools/polyglot_zobrist.py— the Polyglot book hash of spec Annex A in stdlib Python: the public 781-key table vendored with its provenance, pinned in CI against the book format's published test vectors. No runtime dependency, no private repo in the chain.
Maintainer tools
tools/validate.py is the gate CI runs: format, slug
collisions, parent references, depth limits, plus a --strict-chess mode for
legal UCI sequences and SAN tail consistency.
tools/audit_chess.py and
tools/audit_transpositions.py are its batch
counterparts, reporting every issue — or every duplicate-FEN group, ranked by
how likely it is to need a structural decision — instead of stopping at the
first; the resolution workflow is in
docs/archive/transpositions.md. The
attribution factory (audit_naming_attribution.py,
candidate_slice_export.py,
scaffold_attribution_manifest.py,
apply_attribution_manifest.py) triages
rows, exports review slices and applies evidence-backed JSON manifests under
strict guardrails — the catalogue is never hand-edited (see
docs/attribution-batch-engine.md).
tools/fetch_lichess.sh pulls the upstream Lichess
Opening Book TSVs (CC0) into external/. The remaining scripts — the Lichess
parent map, the doc slug gate, and the test suite CI runs on every push
(tools/tests/,
.github/workflows/ci.yml) — are listed with the
documents that use them in docs/INDEX.md.
Three relations per slug
Each catalogue row carries three relations to other slugs:
parent_ocn1— nominal hierarchy. Groups slugs by literature lineage and lets a reader navigate from a family root down to a specific tabiya. The parent chain is what produces a readable slug (E.Nim.Rub.O-O.Nf3is a child ofE.Nim.Rub.O-O).transposes_to— canonicalisation by position, asymmetric. Points from a slug whose FEN coincides with another slug's FEN to the FEN-canonical one. Set when this row is a move-order transposition of another. NULL when this row is itself canonical.same_as— co-canonical preservation, symmetric. Pipe-separated list of slugs that share this row's FEN and are preserved as co-canonicals (both / all are real literary names — e.g. Rubinstein Opening ⇄ Colle-Zukertort). Mutually exclusive withtransposes_toon a single row.
A position-indexed consumer (e.g. chess-parquet) should follow
transposes_to once to canonicalise an OCN-1 result; rows linked
by same_as are all canonical and may be returned together. A
literature-oriented consumer (a book, a teaching tool) should follow
parent_ocn1 to render the human hierarchy. The validator and
audit_transpositions.py enforce that both transposes_to and
same_as only point to rows whose FEN matches.
Compatibility with ECO
OCN-1 does not deprecate ECO. The catalogue records, for every entry, the ECO codes that the slug covers. Tools and consumers SHOULD support both:
- given an ECO code, look up the OCN-1 slug;
- given an OCN-1 slug, look up the ECO codes.
Books, ChessBase, Lichess Opening Book, FIDE rating reports, and any other system that uses ECO continues to work unchanged. OCN is the hierarchical layer on top.
Borderline classifications
Some openings sit awkwardly between ECO classes. OCN-1 makes explicit choices, and these are where its class letter differs from ECO's:
- French (
B.Freand its subtree) isB, the semi-open class, although ECO codes it C00-C19. Rationale: OCN readsCas the symmetric king-pawn openings (1.e4 e5); the French answers 1.e4 asymmetrically, like the Sicilian and the Caro-Kann. At 252 rows this is the largest single divergence, and the one most worth arguing about. - London / Colle family (
A.Lon,A.Coland neighbours) isAalthough ECO codes them in the D range (D02-D05). Rationale: they are queen's-pawn systems, played largely regardless of Black's reply, rather than Queen's Gambit theory (82 rows). - Catalan is
Dwhen Black plays ...d5 within the first five moves. Without ...d5, the position isE(Indian setup against the Catalan bishop). - Grünfeld is
E(Indian) even though most legacy ECO codes place it in the D range (D70-D99). Rationale: Grünfeld is structurally an Indian defence, and grouping it with KID, Nimzo, QID, Bogo gives a cleaner parent-child hierarchy. - Benoni / Benko is
E(Indian) even though legacy ECO places it in the A range. Rationale: the main Benoni and Benko families are Indian defences by structure and move-order (1.d4 Nf6 2.c4 c5), so they belong beside KID and Grünfeld rather than under flank openings.
See spec/OCN-1.md for the full reasoning.
Roadmap
The living plan is docs/traction-roadmap.md:
five horizons — a public-ready gate, exist and install, prove it, announce,
grow — taking OCN from a released catalogue to a standard people can find,
install and cite. Two long-running tracks continue under it: naming
attribution (are the eponyms true, and is the kind of attribution
explicit — invented, published, popularised, event anchor? Methodology:
docs/naming-attribution-audit-methodology.md)
and the internationalised alias sidecars, where the English canonical_name
stays definitive. Release records, decision logs and the rest of the
documentation are indexed in docs/INDEX.md.
Acknowledgements
OCN-1 builds on:
- Šahovski Informator (1971) for the A/B/C/D/E classification.
- lichess-org/chess-openings (CC0) for canonical English names of the long tail.
- Hooper & Whyld, Oxford Companion to Chess (1984), for naming conventions.
License
Dual-licensed:
Copyright © 2026 Club d'Escacs Figueres.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ocn_chess-1.2.1.tar.gz.
File metadata
- Download URL: ocn_chess-1.2.1.tar.gz
- Upload date:
- Size: 787.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e372fa39d59301bfd3f6b7a33cf8777894ee1d82bd21302d961c0c691a820f6d
|
|
| MD5 |
124ae712af2afa24da3ec09c50ff45f2
|
|
| BLAKE2b-256 |
657d7df8338f6b6fecd0d2eb4ab299a7874203458700d121ef5077cd9351e480
|
File details
Details for the file ocn_chess-1.2.1-py3-none-any.whl.
File metadata
- Download URL: ocn_chess-1.2.1-py3-none-any.whl
- Upload date:
- Size: 777.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fcd4ac0b4948f1d2c29e56fafdae63123b2e3c9b8f70dbc9968bdb1364838c03
|
|
| MD5 |
7151315bded8fa27c7d24698cc74e27e
|
|
| BLAKE2b-256 |
e2b1e4b5ecc0fa5fc9ca8e5efee0eac2a835069bbd66a38aaeba2ab7452ce6c7
|