echr-py
Acquire multilingual ECtHR case law, preserve its legal structure, resolve citations to exact documents and paragraphs, search locally, build datasets, and export publication-ready graphs.
echr-py is a research engineering toolkit for the case law of the European
Court of Human Rights. It connects public
HUDOC and
HUDOC-EXEC acquisition to typed, source-addressed
research artifacts through Python, a command-line interface, and the Model
Context Protocol (MCP).
It is designed for work where provenance matters. A result can retain the exact HUDOC document, language version, section, paragraph, individual opinion, footnote, citation span, cited procedural document, cited-paragraph pinpoint, source checksum, and producing software revision.
The core acquisition, parsing, citation, and graph pipeline is deterministic.
Acquisition is concurrent and bounded. In a recorded July 2026 public-HUDOC run it hydrated nine documents and wrote 478,151 characters of source-ordered text in about 1.1 seconds at concurrency twelve, with the exact query, settings, item IDs, hashes, and elapsed time recorded alongside the figure.
One inspectable chain runs from a HUDOC record to a verified paragraph edge. Each stage keeps its own identity, so a later stage never overwrites an earlier one:
| Stage | Identity retained at that stage |
|---|---|
| HUDOC record | item ID, ECLI, language, source checksum |
| Legal spine | canonical sections, stable block IDs, paragraph addresses, typography |
| Source identity | majority, individual opinion, footnote, invoking paragraph |
| Citation occurrence | exact printed span, supporting evidence, pinpoint ownership |
| Exact target | cited document, cited application, or an explicit unresolved scope |
| Target paragraph | printed pinpoint mapped to a verified block in the cited document |
That is the central idea: retain enough structure and provenance to move from a public HUDOC record to a multilingual paragraph, a printed citation occurrence, an exact target document, and, when the source supplies a pinpoint, a verified target paragraph. Deterministic identity and graph layers stay separate from optional, evidence-verified labels.
Start here
python -m pip install "echr-py[citations,analysis]"
Build a rich case artifact and an offline paragraph-citation viewer:
echr-py corpus build --appno 46221/99 --rich-sections \
--citations --out ocalan/
echr-py citations locate --in ocalan/cases.parquet \
--resolution-dir ocalan/citations --scope inclusive \
--out ocalan/citations
echr-py graph export --kind citation-paragraph --format html \
--in ocalan/citations --out ocalan/paragraph-citations.html
Metadata page size and full-text download concurrency are separately tunable; see acquisition performance and tuning.
Live bounded acquisition
In the recorded July 2026 public-HUDOC run, echr-py selected twelve metadata records,
hydrated the nine records for which that response supplied usable official
text, parsed 1,578 source-order blocks, and wrote 478,151 text characters in
about 1.1 seconds with concurrency twelve. This is an observed demonstration,
not a service guarantee; the provenance record
contains the exact query, settings, item IDs, hashes and elapsed time.
Or use the Python API:
from hudoc_py import fetch_case, search
cases = search(
article="3",
respondent="ITA",
date_from="2020-01-01",
importance=[1, 2],
languages=("ENG", "FRE"),
limit=250,
)
cases.to_parquet("italy-article-3.parquet")
ocalan = fetch_case(
appno="46221/99",
language="ENG",
with_text=True,
rich_sections=True,
)
print(ocalan.sections.the_law[:500])
print(ocalan.sections.bench.judges)
print(ocalan.sections.opinions)
The async API mirrors the synchronous surface:
from hudoc_py import aio
cases = await aio.search(article="8", respondent="FRA", limit=50)
What becomes possible
| Research task | What echr-py provides |
|---|---|
| Build a multilingual case-law corpus | Frozen selections, complete language-version discovery, resumable acquisition, checksums, typed failures, Parquet/JSONL/CSV/XLSX |
| Study precedent at paragraph level | Exact printed citation spans, uniquely corroborated procedural targets, citation-owned pinpoints, verified cited paragraphs |
| Compare majority and individual opinions | Stable opinion identities, type, ordinal, authors, joiners, source spans, and separate citation provenance |
| Analyse footnote citation practice | Linked footnote bodies, invoking paragraphs, majority/opinion context, and footnote-owned citation edges |
| Search concepts across English and French | SQLite FTS5, exact dense search, deterministic hybrid fusion, filters, stable paragraph IDs |
| Create a labelled research dataset | Bounded YAML studies, structured outputs, exact evidence quotations, budgets, checkpoints, validation, JSONL/Parquet export |
| Explore citation and custom networks | One graph contract with JSON, GEXF, and fully offline interactive HTML |
| Acquire implementation material | Typed HUDOC-EXEC case and document search, resumable source downloads, text/Markdown conversion, OCR, and manifests |
The citation contract
Most citation networks stop at an application number or an edge between two
cases. echr-py keeps the layers researchers actually need separate:
SCL decision graph
└── Court-supplied, selective cited-authority baseline
Inclusive occurrence ledger
└── every occurrence accepted by the deterministic discovery rules
├── majority / procedure / facts / operative / appendix
├── individual opinion identity and authors
└── linked footnote identity and invoking paragraph
Paragraph graph
└── source paragraph → exact cited document → verified cited paragraph
This separation matters. One citation in a paragraph may carry § 54, while
the next carries no pinpoint; two opinions may cite the same target for
different propositions; and a footnote may cite an authority that never appears
in the judgment's SCL field. echr-py preserves those distinctions instead of
collapsing them into one count.
SCL is valuable bibliographic evidence, but it is not an exhaustive inventory of printed citations – including in the majority judgment. Inclusive discovery therefore starts from SCL without treating its absence as evidence that a paragraph contains no citation.
Occurrence v3 separates a target-independent printed locus_id from
authority-specific occurrence_id rows. Compound references share a locus and
citation_group_id; each authority row retains its ordinal and independently
owned pinpoint. Footnote occurrences keep the physical footnote address and
structured addresses for every invoking paragraph or opinion.
| A bilingual source context with independently owned citation pinpoints. Provenance. | The SCL, occurrence, and paragraph layers remain separately versioned. Provenance. |
Resolution
The resolver combines printed SCL evidence, application numbers, ECLIs, HUDOC
item IDs, document dates and types, procedural phases, reporter references, a
packaged bilingual citation authority containing 42,161 official English and
French rows plus six documented supplements, and a checksummed historical
catalogue.
It does not zip the unordered sclappnos pool to SCL fragments or promote an
ambiguous application to an arbitrary judgment.
In a fixed July 2026 audit using authority parser 6 and the 10 July English authority, the then-current resolver assigned a document automatically to 2,479 of 2,492 SCL mentions (99.5%). That percentage measures automatic resolution coverage of the Court-supplied SCL sample only. It does not measure whether every assignment was correct, detector recall, or the share of all citations printed in the judgments. Thirteen references remained outside the graph pending review or documented exclusion. The sample, denominators, failure classes, method, and qualifications are in the citation-resolution audit.
Discovery beyond SCL
Full-text discovery parses bounded English and French citation envelopes,
application numbers with compatible case names, ECLIs, reporters, dates,
procedural phases, and §, §§, para. or paras. pinpoints. Once a strong
anchor identifies an authority, a document-local gazetteer can recover later
short forms under conservative uniqueness and context rules.
External, self-referential, ambiguous, and rejected candidates remain typed diagnostics. Only exact document resolutions enter the inclusive document graph; only verified target paragraphs enter the paragraph graph.
Commission-era report and admissibility references with Commission-specific
dates or complete D.R. locators are retained as classified, unresolved
occurrences. They cannot be silently promoted to a nearby Court judgment.
Rich legal structure
rich_sections=True builds a source-order document spine rather than a bag of
paragraph strings. It preserves:
- procedure, facts, complaints, law, operative text, and appendices;
- stable physical-block and legal-paragraph addresses, including HTML continuations and repeated printed numbers;
- inline bold and italic runs with offsets;
- linked footnote anchors and multi-block bodies;
- separate-opinion boundaries, types, authors, joiners, and source spans;
- the deciding bench, independently of dissent authors;
- represented-by metadata supplied by HUDOC;
- individual dispositive rulings and recorded votes.
Plain-text inputs remain supported. HTML/DOCX sources provide the richer typography and footnote evidence.
echr-py versions list \
--ecli ECLI:CE:ECHR:2005:0512JUD004622199
echr-py versions download \
--ecli ECLI:CE:ECHR:2005:0512JUD004622199 \
--formats html,txt,md,docx --out ocalan/
The acquisition manifest records each language-specific item ID, rendition type, requested-format outcome, HTTP status, path, byte length, and SHA-256.
Local lexical, dense, and hybrid retrieval
Build a portable paragraph index from any acquired corpus:
echr-py local index-paragraphs --data-dir corpus/ \
--database corpus/paragraphs.sqlite
echr-py embeddings build --database corpus/paragraphs.sqlite \
--provider sentence-transformers \
--model intfloat/multilingual-e5-base \
--model-revision EXACT_COMMIT \
--section facts --section the_law --section separate_opinion \
--include-footnotes --out corpus/embeddings/
echr-py local paragraphs '"positive obligations"' \
--database corpus/paragraphs.sqlite --mode hybrid \
--embeddings corpus/embeddings/ --limit 25
Dense indexes are normalized float32 vectors in Parquet. The manifest records the provider, exact model revision, dimensions, query and passage prefixes, section filter, source database hash, package version, and artifact checksum. Chunked exact cosine search is always available; FAISS is an optional, rebuildable accelerator. Hybrid mode uses deterministic reciprocal-rank fusion and returns lexical, dense, and fused scores separately.
Rich hudoc-paragraphs.v3 rows carry opinion type, authors, joiners, and
footnote identity directly into retrieval results.
Optional evidence-grounded studies
The study runner is for creating datasets, not for handing control of the pipeline to an open-ended agent. A versioned YAML specification fixes:
- corpus and unit of analysis;
- ordered stages and retrieval settings;
- provider and exact model for every model-backed stage;
- JSON Schema output;
- evidence requirements;
- request, token, and dollar budgets;
- output and checkpoint locations.
echr-py study init citation-use \
--source corpus/citations/occurrences.parquet \
--provider openai --model YOUR_EXPLICIT_MODEL \
--taxonomy multiaxial --out citation-use.yaml
echr-py study validate citation-use.yaml
echr-py study plan citation-use.yaml --out runs/citation-use/
echr-py study run citation-use.yaml --out runs/citation-use/
Facts can be required to carry exact source quotations. Offsets are checked against the addressed source text; ambiguous repeated quotations and invalid evidence are rejected. Runs are resumable and model attempts remain distinguishable.
Supported structured-generation surfaces include Gemini, official OpenAI, OpenAI-compatible endpoints, and Anthropic. Native batch execution is explicit for Gemini and official OpenAI; local Sentence Transformers are available for embeddings. No provider or model is selected silently.
Graphs that travel with the research
hudoc-graph/v1 represents nodes and links once and exports them as:
- typed JSON for downstream code;
- GEXF for Gephi;
- a single offline HTML file with vendored D3 and no CDN request.
Adapters cover the SCL decision graph, inclusive citations, paragraph citations, and custom NetworkX graphs.
echr-py graph export --kind citation-scl \
--format gexf --in corpus/citations --out scl.gexf
echr-py graph export --kind citation-paragraph \
--format html --in corpus/citations \
--source-component opinion --out opinions.html
The browser viewer provides search, attribute filters, legends, direction and weight controls, component isolation, node-size and colour mappings, detail panels, and explicit top-N pruning.
| The complete reviewed SCL one-hop network for ten source documents. | The same frozen artifacts summarized as the most-cited target authorities. |
The pinned Article 3/Italy demonstration resolves all 1,124 SCL mentions in its ten source documents: 1,111 automatically and thirteen through documented, source-specific review overrides. Its provenance manifest records the query, source IDs, authority edition, counts, hashes, and generation date.
HUDOC-EXEC
The acquisition client connects merits judgments to the official Committee of Ministers supervision material while preserving HUDOC-EXEC metadata:
from hudoc_py.execution import fetch_case, search, search_documents
open_cases = search(
state="ITA",
supervision="enhanced",
is_closed=False,
limit=50,
)
execution = fetch_case("57818/09", with_documents=True)
plans = search_documents(collection="acp", appno="57818/09", limit=20)
echr-py exec search --state ITA --supervision enhanced \
--out execution.jsonl
echr-py exec search-documents --collection acp --appno 57818/09 \
--out plans.jsonl
echr-py exec fetch-case 57818/09 --out execution-case.json
echr-py exec download-raw --in plans.jsonl --out sources/ \
--concurrency 10 --extract-text
HUDOC-EXEC support is a source-acquisition interface for building a traceable local collection. It discovers official cases and documents, downloads their source files, converts them to text or Markdown, and records the identifiers, checksums, and resumable manifests needed for reproducible work. Records are left unlabelled: research categories and substantive interpretation belong to the downstream project rather than the acquisition client.
Interfaces and commands
The same typed models and artifact identities are exposed through Python, the CLI, and MCP.
With uv and Claude Code installed, Claude Code can register the
version-pinned, read-only MCP server
without cloning the repository:
claude mcp add --scope user --transport stdio echr-py -- \
"$(command -v uvx)" --from 'echr-py[mcp]==0.2.3' echr-py mcp
claude mcp get echr-py
The server itself needs no Anthropic API key. A one-click Claude Desktop MCPB will be attached only after the matching PyPI package and clean-machine bundle tests are complete; manual and source-checkout setup is documented in the MCP guide.
The recorded July 2026 workflow below replays a fixed three-tool protocol: it identifies the exact Öcalan Grand Chamber judgment, recovers its structured spine, bench and separate opinions, then performs inclusive deterministic citation discovery and resolution. It uses real public HUDOC responses, but no model chooses the tools.
That run recovered 553 source blocks, seventeen deciding-bench members, three individual opinions and 139 citation occurrences, including 24 opinion occurrences and 79 occurrence-owned paragraph pinpoints. Its provenance record retains the three tool arguments, timings, exact item ID, package version, and script hashes.
In the retained July 2026 model-driven test, Claude Sonnet 5 made three read-only
echr-py calls: it selected the English judgment by exact ECLI, requested
compact structural evidence, and asked echr-py to construct an
occurrence-weighted citation network.
This is recorded output, not a mock terminal. The run found 139 citation
occurrences across 78 resolved target documents: 115 in the majority text and
24 in separately identified opinions, with 79 occurrence-owned pinpoints. The
top-five counts and resolution fraction in the animation are arithmetic over
the tool response. The checked final answer confines itself to arithmetic over
the returned network and makes no doctrinal claim. Inspect the portable
JSON,
GEXF, or
downloadable single-file interactive HTML,
which runs locally without a server or CDN,
and the compact run provenance.
Claude Code reported claude-sonnet-5 as the reasoning model and a small
Haiku routing call separately.
The MCP surface makes the same source-addressed search, paragraph, case, citation, and graph operations available to an assistant without introducing a second data model. Compact structure and graph tools avoid forcing model clients to ingest entire judgments. The default server is read-only; bounded study jobs are an explicit opt-in.
| Command group | Purpose |
|---|---|
search, count, fetch-case, smart-fetch |
HUDOC metadata and document acquisition |
versions …, corpus … |
Language versions and reproducible corpora |
local …, embeddings … |
Offline browsing and paragraph retrieval |
gui |
Optional local Streamlit corpus browser |
| `citations authority | catalog |
| `graph metrics | export |
| `study validate | plan |
exec … |
HUDOC-EXEC record discovery, download, and source conversion |
mcp |
Read-only MCP server by default; bounded jobs are opt-in |
echr-py --help
echr-py citations --help
echr-py graph export --help
echr-py study --help
echr-py exec --help
The default MCP server exposes search, retrieval, case structure, citations,
and graphs without mutating external state. Study jobs require an explicit
--enable-jobs launch configuration with permitted roots, provider and hook
allowlists, pricing, and per-job budgets.
See also
Other projects working on ECtHR material:
Benchmark
echr-py is compared against the curated Mumford annotation set. Those
annotations are useful for studying selected citation treatments; they are not
an exhaustive citation inventory or a target-paragraph graph. The annotation
frame deliberately omits submissions, general-principles material, facts,
procedure, operative text, appendices, individual opinions, and footnotes, so a
full echr-py run can return citation occurrences from sections and components
outside that frame. Any recovery figure measures recovery of those selected
annotations, not detector precision or completeness over the judgments.
The importer and the methodological comparison remain available for researchers who need them, and the measured figures, denominators, hashes and reproduction records are kept in the methodology audit and the frozen benchmark manifest, where they can carry their conditions, rather than on the project homepage.
The evidence and dates behind external comparisons are maintained in the claim audit.
Installation options
Install only the components a project needs:
# Base HUDOC and HUDOC-EXEC client
python -m pip install echr-py
# Citation resolution and graph analysis
python -m pip install "echr-py[citations,analysis]"
# Research studies and a provider
python -m pip install "echr-py[research-agent,llm-openai]"
# Local dense retrieval
python -m pip install "echr-py[embeddings-local]"
# All user-facing extras
python -m pip install "echr-py[all]"
# Development checkout and validation
git clone https://github.com/niccoloridi/echr-py.git
cd echr-py
python -m pip install -e ".[all,dev]"
Names
The distribution and primary command are echr-py. The Python import is
hudoc_py, and the earlier hudoc-py command still works; both remain
available so existing code and scripts keep running.
import hudoc_py # import name
echr-py --version # primary command
hudoc-py --version # legacy alias, same program
Documentation
- Worked examples
- Python API
- CLI reference
- Source-aware text segmentation
- Citation resolution
- Paragraph-aware citation occurrences
- Deciding benches, judges, and opinions
- Portable retrieval
- Bounded research studies
- Graph export
- HUDOC-EXEC acquisition
- MCP
- Release data and feature boundary
- Development
- Changelog
- Security policy
Verification
python -m pip install -e ".[all,dev]"
ruff check .
mypy hudoc_py
pytest -q
The offline suite requires no API credentials, model downloads, or network. Live HUDOC, HUDOC-EXEC, provider, browser, and OCR tests are opt-in. Release CI builds the wheel and source distribution, checks both against the public feature boundary, installs the wheel in an isolated environment, verifies public imports, and produces provenance attestations.
Funding
This research forms part of the Human Rights Nudge project that has received funding from the European Research Council (ERC) under the European Union’s Horizon 2020 research and innovation programme (Grant agreement No. 803981).
This work was also supported by a King’s Digital Futures Institute Fellowship.
Data responsibility
HUDOC and HUDOC-EXEC are evolving public systems. Preserve item IDs, ECLIs, languages, retrieval dates, checksums, and the producing package revision. Verify quotations, procedural status, and authoritative documents before legal or empirical reliance. Court texts and third-party translations retain their own source terms.
echr-py is an independent research tool, not an official Council of Europe
or European Court of Human Rights product.
Licence
MIT for the project's original software. The licence does not cover upstream Court texts, database contents, independently published datasets, or vendored components such as D3; see Rights and third-party material.
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 echr_py-0.2.3.tar.gz.
File metadata
- Download URL: echr_py-0.2.3.tar.gz
- Upload date:
- Size: 15.5 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1b09997b9244fc00c33da620374797173cae79baeca0638eb54e946a768a0de5
|
|
| MD5 |
887c99d267f42d158a0061bf6f2b4b13
|
|
| BLAKE2b-256 |
12378f38cc1100050badfa88e852fb059357ec3e607870173369e8a5293a3347
|
Provenance
The following attestation bundles were made for echr_py-0.2.3.tar.gz:
Publisher:
publish.yml on niccoloridi/echr-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
echr_py-0.2.3.tar.gz -
Subject digest:
1b09997b9244fc00c33da620374797173cae79baeca0638eb54e946a768a0de5 - Sigstore transparency entry: 2618996790
- Sigstore integration time:
-
Permalink:
niccoloridi/echr-py@c964df62f469676980bf83c5cef44ab7beb531e4 -
Branch / Tag:
refs/tags/v0.2.3 - Owner: https://github.com/niccoloridi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c964df62f469676980bf83c5cef44ab7beb531e4 -
Trigger Event:
release
-
Statement type:
File details
Details for the file echr_py-0.2.3-py3-none-any.whl.
File metadata
- Download URL: echr_py-0.2.3-py3-none-any.whl
- Upload date:
- Size: 15.7 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a87b43bc93127b7865333d33532149d50760e54cbb74ec65760bcf0089f1e802
|
|
| MD5 |
34c255ff97887e6258dabe5375c339b3
|
|
| BLAKE2b-256 |
5323aff7e48bb2c2fb549ddbb15582bdaf48faf95e0ddcbe881265a670c01313
|
Provenance
The following attestation bundles were made for echr_py-0.2.3-py3-none-any.whl:
Publisher:
publish.yml on niccoloridi/echr-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
echr_py-0.2.3-py3-none-any.whl -
Subject digest:
a87b43bc93127b7865333d33532149d50760e54cbb74ec65760bcf0089f1e802 - Sigstore transparency entry: 2618996830
- Sigstore integration time:
-
Permalink:
niccoloridi/echr-py@c964df62f469676980bf83c5cef44ab7beb531e4 -
Branch / Tag:
refs/tags/v0.2.3 - Owner: https://github.com/niccoloridi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c964df62f469676980bf83c5cef44ab7beb531e4 -
Trigger Event:
release
-
Statement type: