echr-py 0.2.2__tar.gz → 0.2.3__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.
- {echr_py-0.2.2 → echr_py-0.2.3}/CITATION.cff +1 -1
- {echr_py-0.2.2/echr_py.egg-info → echr_py-0.2.3}/PKG-INFO +18 -7
- {echr_py-0.2.2 → echr_py-0.2.3}/README.md +17 -6
- {echr_py-0.2.2 → echr_py-0.2.3/echr_py.egg-info}/PKG-INFO +18 -7
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/__init__.py +1 -1
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/bilingual/__init__.py +2 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/bilingual/rescue.py +62 -2
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/text/opinions.py +62 -7
- {echr_py-0.2.2 → echr_py-0.2.3}/pyproject.toml +1 -1
- {echr_py-0.2.2 → echr_py-0.2.3}/LICENSE +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/MANIFEST.in +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/RIGHTS.md +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/SECURITY.md +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/echr_py.egg-info/SOURCES.txt +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/echr_py.egg-info/dependency_links.txt +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/echr_py.egg-info/entry_points.txt +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/echr_py.egg-info/requires.txt +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/echr_py.egg-info/top_level.txt +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/_aio.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/_sync.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/bilingual/bundle.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/bilingual/corpus.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/bilingual/ecli.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/bilingual/reconcile.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/cache/__init__.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/cache/store.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/citations/__init__.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/citations/artifacts.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/citations/authority.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/citations/benchmarks.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/citations/catalog.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/citations/extractor.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/citations/graph.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/citations/metrics.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/citations/models.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/citations/occurrences.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/citations/paragraphs.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/citations/reporter.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/citations/resolver.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/cli.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/config.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/data/__init__.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/data/citation_authority.csv +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/data/citation_authority.json +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/data/citation_authority_eng.csv +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/data/citation_authority_eng.json +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/data/citation_authority_supplements.json +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/data/historical_citation_catalog.json +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/data/kpthesaurus_eng.json +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/execution/__init__.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/execution/_sync.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/execution/aio.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/execution/bridge.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/execution/client.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/execution/collections.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/execution/downloader.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/execution/imports.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/execution/queries.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/execution/raw_download.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/execution/related_scraper.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/execution/textract.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/extractors/__init__.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/extractors/base.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/graphs/__init__.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/graphs/adapters.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/graphs/assets/LICENSE-D3.txt +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/graphs/assets/d3.v7.min.js +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/graphs/export.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/graphs/models.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/graphs/template.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/gui/__init__.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/gui/app.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/llm/__init__.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/llm/base.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/llm/batch.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/llm/client.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/llm/providers.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/local/__init__.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/local/browse.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/local/codebook.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/local/export.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/local/paragraphs.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/local/registry.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/local/search.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/main/__init__.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/main/client.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/main/downloader.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/main/dsl.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/main/queries.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/main/url.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/mcp/__init__.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/mcp/__main__.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/mcp/install.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/mcp/server.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/models/__init__.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/models/case.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/models/citation.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/models/common.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/models/document.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/models/exec_case.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/models/exec_document.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/models/version.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/pipeline/__init__.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/pipeline/runner.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/pipeline/validation.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/retrieval/__init__.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/retrieval/benchmark.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/retrieval/embeddings.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/retrieval/hybrid.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/retrieval/providers.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/studies/__init__.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/studies/batch.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/studies/hooks.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/studies/jobs.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/studies/models.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/studies/runner.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/studies/schemas/citation-use-minimal.json +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/studies/schemas/citation-use-multiaxial.json +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/studies/spec.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/studies/taxonomies.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/text/__init__.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/text/composition.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/text/conversion.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/text/dispositive.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/text/judges.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/text/segmentation.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/text/spine.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/thesaurus.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/utils/__init__.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/utils/downloads.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/utils/jsonl.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/utils/retry.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/utils/runlog.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/hudoc_py/versions.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/release-requirements.txt +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/scripts/check_sdist.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/scripts/check_wheel.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/scripts/public_package_files.txt +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/scripts/public_payload_hashes.json +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/scripts/verify_release.py +0 -0
- {echr_py-0.2.2 → echr_py-0.2.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: echr-py
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Pythonic acquisition of HUDOC and HUDOC-EXEC metadata and source documents for ECtHR research.
|
|
5
5
|
Author-email: Niccolo Ridi <niccoloridi@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -102,7 +102,7 @@ Dynamic: license-file
|
|
|
102
102
|
<a href="https://github.com/niccoloridi/echr-py/actions/workflows/ci.yml"><img src="https://github.com/niccoloridi/echr-py/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
103
103
|
<img src="https://img.shields.io/badge/python-3.11%E2%80%933.14-3776AB.svg" alt="Python 3.11–3.14">
|
|
104
104
|
<a href="https://github.com/niccoloridi/echr-py/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="MIT license"></a>
|
|
105
|
-
<img src="https://img.shields.io/badge/version-0.2.
|
|
105
|
+
<img src="https://img.shields.io/badge/version-0.2.3-blue.svg" alt="Version 0.2.3">
|
|
106
106
|
</p>
|
|
107
107
|
|
|
108
108
|
`echr-py` is a research engineering toolkit for the case law of the European
|
|
@@ -112,10 +112,6 @@ Court of Human Rights. It connects public
|
|
|
112
112
|
research artifacts through Python, a command-line interface, and the Model
|
|
113
113
|
Context Protocol (MCP).
|
|
114
114
|
|
|
115
|
-
The distribution and primary command are now named `echr-py`. The established
|
|
116
|
-
`hudoc_py` Python import and the legacy `hudoc-py` command remain available for
|
|
117
|
-
backward compatibility.
|
|
118
|
-
|
|
119
115
|
It is designed for work where provenance matters. A result can retain the exact
|
|
120
116
|
HUDOC document, language version, section, paragraph, individual opinion,
|
|
121
117
|
footnote, citation span, cited procedural document, cited-paragraph pinpoint,
|
|
@@ -495,7 +491,7 @@ without cloning the repository:
|
|
|
495
491
|
|
|
496
492
|
```bash
|
|
497
493
|
claude mcp add --scope user --transport stdio echr-py -- \
|
|
498
|
-
"$(command -v uvx)" --from 'echr-py[mcp]==0.2.
|
|
494
|
+
"$(command -v uvx)" --from 'echr-py[mcp]==0.2.3' echr-py mcp
|
|
499
495
|
claude mcp get echr-py
|
|
500
496
|
```
|
|
501
497
|
|
|
@@ -635,6 +631,21 @@ cd echr-py
|
|
|
635
631
|
python -m pip install -e ".[all,dev]"
|
|
636
632
|
```
|
|
637
633
|
|
|
634
|
+
### Names
|
|
635
|
+
|
|
636
|
+
The distribution and primary command are `echr-py`. The Python import is
|
|
637
|
+
`hudoc_py`, and the earlier `hudoc-py` command still works; both remain
|
|
638
|
+
available so existing code and scripts keep running.
|
|
639
|
+
|
|
640
|
+
```python
|
|
641
|
+
import hudoc_py # import name
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
```bash
|
|
645
|
+
echr-py --version # primary command
|
|
646
|
+
hudoc-py --version # legacy alias, same program
|
|
647
|
+
```
|
|
648
|
+
|
|
638
649
|
## Documentation
|
|
639
650
|
|
|
640
651
|
- [Worked examples](https://github.com/niccoloridi/echr-py/blob/main/docs/examples.md)
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<a href="https://github.com/niccoloridi/echr-py/actions/workflows/ci.yml"><img src="https://github.com/niccoloridi/echr-py/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
15
15
|
<img src="https://img.shields.io/badge/python-3.11%E2%80%933.14-3776AB.svg" alt="Python 3.11–3.14">
|
|
16
16
|
<a href="https://github.com/niccoloridi/echr-py/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="MIT license"></a>
|
|
17
|
-
<img src="https://img.shields.io/badge/version-0.2.
|
|
17
|
+
<img src="https://img.shields.io/badge/version-0.2.3-blue.svg" alt="Version 0.2.3">
|
|
18
18
|
</p>
|
|
19
19
|
|
|
20
20
|
`echr-py` is a research engineering toolkit for the case law of the European
|
|
@@ -24,10 +24,6 @@ Court of Human Rights. It connects public
|
|
|
24
24
|
research artifacts through Python, a command-line interface, and the Model
|
|
25
25
|
Context Protocol (MCP).
|
|
26
26
|
|
|
27
|
-
The distribution and primary command are now named `echr-py`. The established
|
|
28
|
-
`hudoc_py` Python import and the legacy `hudoc-py` command remain available for
|
|
29
|
-
backward compatibility.
|
|
30
|
-
|
|
31
27
|
It is designed for work where provenance matters. A result can retain the exact
|
|
32
28
|
HUDOC document, language version, section, paragraph, individual opinion,
|
|
33
29
|
footnote, citation span, cited procedural document, cited-paragraph pinpoint,
|
|
@@ -407,7 +403,7 @@ without cloning the repository:
|
|
|
407
403
|
|
|
408
404
|
```bash
|
|
409
405
|
claude mcp add --scope user --transport stdio echr-py -- \
|
|
410
|
-
"$(command -v uvx)" --from 'echr-py[mcp]==0.2.
|
|
406
|
+
"$(command -v uvx)" --from 'echr-py[mcp]==0.2.3' echr-py mcp
|
|
411
407
|
claude mcp get echr-py
|
|
412
408
|
```
|
|
413
409
|
|
|
@@ -547,6 +543,21 @@ cd echr-py
|
|
|
547
543
|
python -m pip install -e ".[all,dev]"
|
|
548
544
|
```
|
|
549
545
|
|
|
546
|
+
### Names
|
|
547
|
+
|
|
548
|
+
The distribution and primary command are `echr-py`. The Python import is
|
|
549
|
+
`hudoc_py`, and the earlier `hudoc-py` command still works; both remain
|
|
550
|
+
available so existing code and scripts keep running.
|
|
551
|
+
|
|
552
|
+
```python
|
|
553
|
+
import hudoc_py # import name
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
```bash
|
|
557
|
+
echr-py --version # primary command
|
|
558
|
+
hudoc-py --version # legacy alias, same program
|
|
559
|
+
```
|
|
560
|
+
|
|
550
561
|
## Documentation
|
|
551
562
|
|
|
552
563
|
- [Worked examples](https://github.com/niccoloridi/echr-py/blob/main/docs/examples.md)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: echr-py
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Pythonic acquisition of HUDOC and HUDOC-EXEC metadata and source documents for ECtHR research.
|
|
5
5
|
Author-email: Niccolo Ridi <niccoloridi@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -102,7 +102,7 @@ Dynamic: license-file
|
|
|
102
102
|
<a href="https://github.com/niccoloridi/echr-py/actions/workflows/ci.yml"><img src="https://github.com/niccoloridi/echr-py/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
103
103
|
<img src="https://img.shields.io/badge/python-3.11%E2%80%933.14-3776AB.svg" alt="Python 3.11–3.14">
|
|
104
104
|
<a href="https://github.com/niccoloridi/echr-py/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="MIT license"></a>
|
|
105
|
-
<img src="https://img.shields.io/badge/version-0.2.
|
|
105
|
+
<img src="https://img.shields.io/badge/version-0.2.3-blue.svg" alt="Version 0.2.3">
|
|
106
106
|
</p>
|
|
107
107
|
|
|
108
108
|
`echr-py` is a research engineering toolkit for the case law of the European
|
|
@@ -112,10 +112,6 @@ Court of Human Rights. It connects public
|
|
|
112
112
|
research artifacts through Python, a command-line interface, and the Model
|
|
113
113
|
Context Protocol (MCP).
|
|
114
114
|
|
|
115
|
-
The distribution and primary command are now named `echr-py`. The established
|
|
116
|
-
`hudoc_py` Python import and the legacy `hudoc-py` command remain available for
|
|
117
|
-
backward compatibility.
|
|
118
|
-
|
|
119
115
|
It is designed for work where provenance matters. A result can retain the exact
|
|
120
116
|
HUDOC document, language version, section, paragraph, individual opinion,
|
|
121
117
|
footnote, citation span, cited procedural document, cited-paragraph pinpoint,
|
|
@@ -495,7 +491,7 @@ without cloning the repository:
|
|
|
495
491
|
|
|
496
492
|
```bash
|
|
497
493
|
claude mcp add --scope user --transport stdio echr-py -- \
|
|
498
|
-
"$(command -v uvx)" --from 'echr-py[mcp]==0.2.
|
|
494
|
+
"$(command -v uvx)" --from 'echr-py[mcp]==0.2.3' echr-py mcp
|
|
499
495
|
claude mcp get echr-py
|
|
500
496
|
```
|
|
501
497
|
|
|
@@ -635,6 +631,21 @@ cd echr-py
|
|
|
635
631
|
python -m pip install -e ".[all,dev]"
|
|
636
632
|
```
|
|
637
633
|
|
|
634
|
+
### Names
|
|
635
|
+
|
|
636
|
+
The distribution and primary command are `echr-py`. The Python import is
|
|
637
|
+
`hudoc_py`, and the earlier `hudoc-py` command still works; both remain
|
|
638
|
+
available so existing code and scripts keep running.
|
|
639
|
+
|
|
640
|
+
```python
|
|
641
|
+
import hudoc_py # import name
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
```bash
|
|
645
|
+
echr-py --version # primary command
|
|
646
|
+
hudoc-py --version # legacy alias, same program
|
|
647
|
+
```
|
|
648
|
+
|
|
638
649
|
## Documentation
|
|
639
650
|
|
|
640
651
|
- [Worked examples](https://github.com/niccoloridi/echr-py/blob/main/docs/examples.md)
|
|
@@ -33,6 +33,7 @@ from .rescue import (
|
|
|
33
33
|
find_french_sibling,
|
|
34
34
|
rescue_candidates,
|
|
35
35
|
rescue_french,
|
|
36
|
+
sibling_conflicts,
|
|
36
37
|
)
|
|
37
38
|
|
|
38
39
|
|
|
@@ -70,6 +71,7 @@ __all__ = [
|
|
|
70
71
|
"ReconcileStats",
|
|
71
72
|
"ReconcileInvariantError",
|
|
72
73
|
"rescue_french",
|
|
74
|
+
"sibling_conflicts",
|
|
73
75
|
"rescue_candidates",
|
|
74
76
|
"find_french_sibling",
|
|
75
77
|
"apply_rescue_mapping",
|
|
@@ -24,7 +24,7 @@ from __future__ import annotations
|
|
|
24
24
|
|
|
25
25
|
import asyncio
|
|
26
26
|
import logging
|
|
27
|
-
from datetime import UTC, datetime
|
|
27
|
+
from datetime import UTC, date, datetime
|
|
28
28
|
from pathlib import Path
|
|
29
29
|
from typing import Literal
|
|
30
30
|
|
|
@@ -86,6 +86,52 @@ def rescue_candidates(collection: list[Case]) -> list[Case]:
|
|
|
86
86
|
return out
|
|
87
87
|
|
|
88
88
|
|
|
89
|
+
def _case_date(case: Case) -> date | None:
|
|
90
|
+
"""The document's own decision date, whichever HUDOC field carries it."""
|
|
91
|
+
return case.kp_date or case.judgement_date or case.decision_date
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def sibling_conflicts(case: Case, sibling: Case) -> list[str]:
|
|
95
|
+
"""Reasons *sibling* cannot be the French counterpart of *case*.
|
|
96
|
+
|
|
97
|
+
An application number identifies an *application*, not a document. The same
|
|
98
|
+
number spans merits, just satisfaction, admissibility and revision
|
|
99
|
+
documents, so a sibling accepted on the number alone can silently pair an
|
|
100
|
+
English merits placeholder with a French admissibility decision.
|
|
101
|
+
|
|
102
|
+
Acceptance is therefore conjunctive and fail-closed: every identity the two
|
|
103
|
+
records both supply must agree. Evidence the sibling does not carry cannot
|
|
104
|
+
create a conflict, but it cannot excuse one either.
|
|
105
|
+
"""
|
|
106
|
+
conflicts: list[str] = []
|
|
107
|
+
|
|
108
|
+
if case.doctype and sibling.doctype and case.doctype != sibling.doctype:
|
|
109
|
+
conflicts.append(f"doctype {case.doctype} != {sibling.doctype}")
|
|
110
|
+
|
|
111
|
+
if (
|
|
112
|
+
case.doctype_branch
|
|
113
|
+
and sibling.doctype_branch
|
|
114
|
+
and case.doctype_branch != sibling.doctype_branch
|
|
115
|
+
):
|
|
116
|
+
conflicts.append(
|
|
117
|
+
f"doctype_branch {case.doctype_branch} != {sibling.doctype_branch}"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
case_date, sibling_date = _case_date(case), _case_date(sibling)
|
|
121
|
+
if case_date and sibling_date and case_date != sibling_date:
|
|
122
|
+
conflicts.append(f"date {case_date.isoformat()} != {sibling_date.isoformat()}")
|
|
123
|
+
|
|
124
|
+
# A shared ECLI payload is the strongest available identity: the language
|
|
125
|
+
# segment differs between siblings, the rest must not.
|
|
126
|
+
if case.ecli and sibling.ecli and case.ecli != sibling.ecli:
|
|
127
|
+
conflicts.append(f"ecli {case.ecli} != {sibling.ecli}")
|
|
128
|
+
|
|
129
|
+
if case.appno and sibling.appno and not set(case.appno) & set(sibling.appno):
|
|
130
|
+
conflicts.append("no shared application number")
|
|
131
|
+
|
|
132
|
+
return conflicts
|
|
133
|
+
|
|
134
|
+
|
|
89
135
|
async def find_french_sibling(
|
|
90
136
|
client: AsyncHudocClient,
|
|
91
137
|
case: Case,
|
|
@@ -98,7 +144,12 @@ async def find_french_sibling(
|
|
|
98
144
|
|
|
99
145
|
Tries each application number in order; returns
|
|
100
146
|
``(french_itemid, appno_used, appnos_tried)`` for the first appno that
|
|
101
|
-
yields a non-placeholder French document distinct from ``case.itemid
|
|
147
|
+
yields a non-placeholder French document distinct from ``case.itemid``
|
|
148
|
+
**and compatible with it** under :func:`sibling_conflicts`.
|
|
149
|
+
|
|
150
|
+
A candidate whose doctype, branch, date, ECLI or application set conflicts
|
|
151
|
+
is skipped rather than returned, so a run abstains instead of pairing an
|
|
152
|
+
English placeholder with the wrong procedural document.
|
|
102
153
|
"""
|
|
103
154
|
tried: list[str] = []
|
|
104
155
|
for appno in case.appno:
|
|
@@ -118,6 +169,15 @@ async def find_french_sibling(
|
|
|
118
169
|
continue
|
|
119
170
|
if sibling.is_placeholder is True:
|
|
120
171
|
continue
|
|
172
|
+
conflicts = sibling_conflicts(case, sibling)
|
|
173
|
+
if conflicts:
|
|
174
|
+
logger.debug(
|
|
175
|
+
"rescue: rejected %s for %s (%s)",
|
|
176
|
+
sibling.itemid,
|
|
177
|
+
case.itemid,
|
|
178
|
+
"; ".join(conflicts),
|
|
179
|
+
)
|
|
180
|
+
continue
|
|
121
181
|
return sibling.itemid, appno, tried
|
|
122
182
|
return None, None, tried
|
|
123
183
|
|
|
@@ -66,6 +66,13 @@ _EN_CONTINUATION_RE = re.compile(
|
|
|
66
66
|
re.VERBOSE,
|
|
67
67
|
)
|
|
68
68
|
|
|
69
|
+
# Legacy conversions lowercase the names tail. Matched only through
|
|
70
|
+
# :func:`_continuation_names`, which requires roster anchoring for that form.
|
|
71
|
+
_EN_CONTINUATION_LOWER_RE = re.compile(
|
|
72
|
+
rf"^\s*(?:OF\s+)?(?:{_EN_TITLE})?(?P<names>\w.*?)\s*$",
|
|
73
|
+
re.VERBOSE | re.IGNORECASE,
|
|
74
|
+
)
|
|
75
|
+
|
|
69
76
|
# --- French heading grammar --------------------------------------------------
|
|
70
77
|
|
|
71
78
|
_FR_TYPE = r"""
|
|
@@ -238,8 +245,14 @@ def _classify_fr(type_full: str) -> OpinionType:
|
|
|
238
245
|
def _looks_like_heading_tail(names: str) -> bool:
|
|
239
246
|
"""Accept a title-case heading but reject a sentence-shaped names tail."""
|
|
240
247
|
tail = names.strip()
|
|
241
|
-
if not tail or
|
|
248
|
+
if not tail or tail.endswith((".", ";", ":")):
|
|
242
249
|
return False
|
|
250
|
+
# The length ceiling separates a names tail from a wrapped prose sentence.
|
|
251
|
+
# A joint opinion of six or more judges legitimately exceeds it, so an
|
|
252
|
+
# over-long tail is not rejected outright: it must clear the roster anchor
|
|
253
|
+
# below, which prose cannot.
|
|
254
|
+
if len(tail) >= 90:
|
|
255
|
+
return _names_all_rostered(tail)
|
|
243
256
|
# A proper name may be title case and contain connectors/particles, but a
|
|
244
257
|
# prose continuation contains other wholly lowercase words.
|
|
245
258
|
without_name_words = re.sub(
|
|
@@ -248,7 +261,31 @@ def _looks_like_heading_tail(names: str) -> bool:
|
|
|
248
261
|
tail,
|
|
249
262
|
flags=re.IGNORECASE,
|
|
250
263
|
)
|
|
251
|
-
|
|
264
|
+
if re.search(r"\b[a-zà-ÿ]{3,}\b", without_name_words) is None:
|
|
265
|
+
return True
|
|
266
|
+
# Legacy HUDOC conversions lowercase an otherwise exact names tail, which
|
|
267
|
+
# is indistinguishable from prose by casing alone. Accept that form only
|
|
268
|
+
# when every parsed name is independently anchored in the verified
|
|
269
|
+
# elected/ad-hoc roster, exactly as :func:`_heading_case_allowed` does for
|
|
270
|
+
# a lowercased single-line heading. Prose tails still fail closed.
|
|
271
|
+
return _names_all_rostered(tail)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _names_all_rostered(tail: str) -> bool:
|
|
275
|
+
"""True when every name in *tail* resolves in the canonical judge roster."""
|
|
276
|
+
from .judges import is_ad_hoc_judge, judge_country
|
|
277
|
+
|
|
278
|
+
stripped = re.sub(
|
|
279
|
+
r"^\s*(?:of|by|des|du|de)\s+(?:the\s+)?(?:judges?|juges?|mr|mrs|ms|sir)\b\.?\s*",
|
|
280
|
+
"",
|
|
281
|
+
tail,
|
|
282
|
+
flags=re.IGNORECASE,
|
|
283
|
+
)
|
|
284
|
+
authors, joined_by = _split_authors_joiners(stripped)
|
|
285
|
+
judges = [*authors, *joined_by]
|
|
286
|
+
return bool(judges) and all(
|
|
287
|
+
judge_country(judge) is not None or is_ad_hoc_judge(judge) for judge in judges
|
|
288
|
+
)
|
|
252
289
|
|
|
253
290
|
|
|
254
291
|
def _heading_case_allowed(prefix: str, names: str) -> bool:
|
|
@@ -300,6 +337,24 @@ def _extend_names(lines: list[str], names: str, end: int, connectors: str) -> tu
|
|
|
300
337
|
return names, end
|
|
301
338
|
|
|
302
339
|
|
|
340
|
+
|
|
341
|
+
def _continuation_names(strict: re.Pattern[str], lenient: re.Pattern[str], line: str) -> str | None:
|
|
342
|
+
"""Names from a heading continuation line, or ``None``.
|
|
343
|
+
|
|
344
|
+
The strict pattern carries the ordinary uppercase contract. The lenient
|
|
345
|
+
pattern additionally accepts a lowercased tail, which legacy HUDOC
|
|
346
|
+
conversions produce, but only when every parsed name is anchored in the
|
|
347
|
+
verified elected/ad-hoc roster.
|
|
348
|
+
"""
|
|
349
|
+
match = strict.match(line)
|
|
350
|
+
if match:
|
|
351
|
+
return match.group("names")
|
|
352
|
+
match = lenient.match(line)
|
|
353
|
+
if match and _names_all_rostered(match.group("names")):
|
|
354
|
+
return match.group("names")
|
|
355
|
+
return None
|
|
356
|
+
|
|
357
|
+
|
|
303
358
|
def _match_header(lines: list[str], i: int) -> _Header | None:
|
|
304
359
|
line = lines[i].strip()
|
|
305
360
|
if not line or _TOC_LINE_RE.search(line):
|
|
@@ -344,11 +399,11 @@ def _match_header(lines: list[str], i: int) -> _Header | None:
|
|
|
344
399
|
continuation = _next_continuation(lines, i) if match else None
|
|
345
400
|
if match and continuation:
|
|
346
401
|
j, continuation_line = continuation
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
)
|
|
402
|
+
cont_names = _continuation_names(
|
|
403
|
+
_EN_CONTINUATION_RE, _EN_CONTINUATION_LOWER_RE, continuation_line
|
|
404
|
+
)
|
|
405
|
+
if cont_names is not None and _heading_case_allowed(line, cont_names):
|
|
406
|
+
names, end = _extend_names(lines, cont_names, j + 1, r"(?:,|\bAND)\s*$")
|
|
352
407
|
return _Header(
|
|
353
408
|
i,
|
|
354
409
|
end,
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "echr-py"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.3"
|
|
8
8
|
description = "Pythonic acquisition of HUDOC and HUDOC-EXEC metadata and source documents for ECtHR research."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|