ccrt 0.1.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 (57) hide show
  1. ccrt-0.1.0/CHANGELOG.md +44 -0
  2. ccrt-0.1.0/CONTRIBUTING.md +24 -0
  3. ccrt-0.1.0/LICENSE +21 -0
  4. ccrt-0.1.0/MANIFEST.in +5 -0
  5. ccrt-0.1.0/PKG-INFO +118 -0
  6. ccrt-0.1.0/README.md +82 -0
  7. ccrt-0.1.0/benchmarks/benchmark.py +57 -0
  8. ccrt-0.1.0/benchmarks/evaluate.py +42 -0
  9. ccrt-0.1.0/benchmarks/smoke_install.py +124 -0
  10. ccrt-0.1.0/ccrt/__init__.py +37 -0
  11. ccrt-0.1.0/ccrt/__main__.py +3 -0
  12. ccrt-0.1.0/ccrt/_version.py +1 -0
  13. ccrt-0.1.0/ccrt/cli.py +205 -0
  14. ccrt-0.1.0/ccrt/client.py +166 -0
  15. ccrt-0.1.0/ccrt/config.py +23 -0
  16. ccrt-0.1.0/ccrt/evaluation.py +30 -0
  17. ccrt-0.1.0/ccrt/export.py +152 -0
  18. ccrt-0.1.0/ccrt/interactive.py +160 -0
  19. ccrt-0.1.0/ccrt/loaders/__init__.py +5 -0
  20. ccrt-0.1.0/ccrt/loaders/acled.py +69 -0
  21. ccrt-0.1.0/ccrt/loaders/base.py +144 -0
  22. ccrt-0.1.0/ccrt/loaders/ucdp.py +75 -0
  23. ccrt-0.1.0/ccrt/merge.py +186 -0
  24. ccrt-0.1.0/ccrt/report.py +170 -0
  25. ccrt-0.1.0/ccrt/review.py +56 -0
  26. ccrt-0.1.0/ccrt/schema.py +162 -0
  27. ccrt-0.1.0/ccrt/storage.py +168 -0
  28. ccrt-0.1.0/ccrt/tui.py +349 -0
  29. ccrt-0.1.0/ccrt.egg-info/PKG-INFO +118 -0
  30. ccrt-0.1.0/ccrt.egg-info/SOURCES.txt +55 -0
  31. ccrt-0.1.0/ccrt.egg-info/dependency_links.txt +1 -0
  32. ccrt-0.1.0/ccrt.egg-info/entry_points.txt +2 -0
  33. ccrt-0.1.0/ccrt.egg-info/requires.txt +15 -0
  34. ccrt-0.1.0/ccrt.egg-info/top_level.txt +1 -0
  35. ccrt-0.1.0/docs/api.md +145 -0
  36. ccrt-0.1.0/docs/releasing.md +95 -0
  37. ccrt-0.1.0/docs/source-mappings.md +101 -0
  38. ccrt-0.1.0/docs/usage.md +135 -0
  39. ccrt-0.1.0/docs/validation.md +117 -0
  40. ccrt-0.1.0/examples/acled_synthetic.csv +6 -0
  41. ccrt-0.1.0/examples/reconciliation_walkthrough.ipynb +274 -0
  42. ccrt-0.1.0/examples/source_a.csv +3 -0
  43. ccrt-0.1.0/examples/source_b.csv +3 -0
  44. ccrt-0.1.0/examples/truth_synthetic.csv +4 -0
  45. ccrt-0.1.0/examples/ucdp_synthetic.csv +5 -0
  46. ccrt-0.1.0/pyproject.toml +53 -0
  47. ccrt-0.1.0/setup.cfg +4 -0
  48. ccrt-0.1.0/tests/conftest.py +33 -0
  49. ccrt-0.1.0/tests/test_cli.py +123 -0
  50. ccrt-0.1.0/tests/test_client.py +153 -0
  51. ccrt-0.1.0/tests/test_confidence.py +15 -0
  52. ccrt-0.1.0/tests/test_config.py +70 -0
  53. ccrt-0.1.0/tests/test_loaders.py +174 -0
  54. ccrt-0.1.0/tests/test_merge.py +131 -0
  55. ccrt-0.1.0/tests/test_report.py +71 -0
  56. ccrt-0.1.0/tests/test_storage_review.py +125 -0
  57. ccrt-0.1.0/tests/test_tui.py +33 -0
@@ -0,0 +1,44 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Added source-preserving UCDP GED CSV/API and ACLED event CSV adapters, validation
6
+ reports, explicit snapshot versions, and input fingerprints.
7
+ - Added bounded authenticated API paging/retries with mocked failure tests and
8
+ verified the live endpoint's zero-based page numbering.
9
+ - Preserved published UCDP fatality estimates that fall outside low/best/high
10
+ numerical order, with a warning for each affected record.
11
+ - Replaced automatic one-to-one assignment with all candidate links, date intervals,
12
+ precision warnings, coarse-location handling, and explicit review decisions.
13
+ - Added JSON run/review bundles, review CSV import/export, nullable pandas tables,
14
+ and metadata-preserving Parquet.
15
+ - Added a resumable terminal reviewer that groups competing candidates and displays
16
+ the original source accounts while saving each decision.
17
+ - Added an optional experimental full-screen review dashboard using terminal curses,
18
+ isolated from the review data model for easy removal.
19
+ - Added review reports with candidate coverage, unique-record counts, unlinked
20
+ reasons, and accepted-link diagnostics. Partial reviews never produce precision
21
+ or recall metrics.
22
+ - Added installed-wheel workflow checks for core and tables installations, and
23
+ extended package CI beyond the CLI help check.
24
+ - Added MIT licensing, CLI subcommands, tutorial notebook, synthetic evaluation,
25
+ benchmark tooling, CI, and package build checks.
26
+
27
+ ### Migration from the unreleased initial prototype
28
+
29
+ `Incident` is replaced by `SourceRecord`; `load_csv` now returns a `Dataset`.
30
+ `reconcile(left, right, config=MatchConfig(...))` accepts datasets and returns
31
+ `.candidates` plus `.unlinked`, replacing greedy `.matches` and unmatched lists.
32
+ Use `total_deaths_low/best/high` and `civilian_deaths` explicitly. The CLI now uses
33
+ `import`, `fetch-ucdp`, `match`, `review`, and `export` subcommands. These changes
34
+ precede the first public release; no backward-compatibility shim is provided.
35
+
36
+ ### Validation scope
37
+
38
+ `0.1.0` is an experimental library. Independent real-data linkage
39
+ evaluation is not a publication gate for the library, but remains necessary before
40
+ claiming empirical matching accuracy. A prior UCDP GED 26.1 Iraq 2020 query
41
+ retrieved 282 records with 10 validation warnings; a real ACLED export supplied
42
+ 6,306 filtered Iraq 2020 records with no import issues. Candidate generation
43
+ produced 1,329 pairs. Partial manual reviews do not establish precision or recall.
44
+ See `docs/releasing.md` for the publication procedure.
@@ -0,0 +1,24 @@
1
+ # Contributing and learning
2
+
3
+ Install `.[tables,dev]` into a virtual environment. Run `python -m pytest`,
4
+ `ruff check .`, and `ruff format --check .` before proposing a change. Tests use
5
+ invented examples and mocked HTTP; they must never require private data or API keys.
6
+
7
+ Work on one behaviour at a time: explain the requirement, inspect its source
8
+ contract, write a test that demonstrates the behaviour, implement it, and review
9
+ its effect on real research assumptions. Good first exercises are in the notebook.
10
+
11
+ New source adapters need a documented field mapping and source codebook reference,
12
+ fixtures that can be distributed, validation for malformed input, and a round-trip
13
+ provenance test. Do not infer civilian deaths from total deaths. Changes to date
14
+ uncertainty or candidate ranking need an updated algorithm ID and evaluation notes.
15
+ Reports of matching quality need independently adjudicated reference labels from a
16
+ declared sample; synthetic metrics and partially reviewed candidates are not
17
+ real-data accuracy estimates. Keep source records and evidence within their access
18
+ and redistribution terms.
19
+
20
+ Open a GitHub issue with the version, a minimal synthetic reproduction, expected
21
+ behaviour, and actual behaviour. Remove credentials and private source material.
22
+ We aim to review contributions as maintainer time allows; no support response SLA
23
+ is offered for the preview. Python 3.10–3.14 are tested in CI; optional pandas and
24
+ PyArrow support is tested separately from the core installation.
ccrt-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fabian Figueroa
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.
ccrt-0.1.0/MANIFEST.in ADDED
@@ -0,0 +1,5 @@
1
+ include LICENSE CHANGELOG.md CONTRIBUTING.md
2
+ include docs/api.md docs/releasing.md docs/source-mappings.md docs/usage.md docs/validation.md
3
+ recursive-include examples *.csv *.ipynb
4
+ recursive-include tests *.py
5
+ recursive-include benchmarks *.py
ccrt-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,118 @@
1
+ Metadata-Version: 2.4
2
+ Name: ccrt
3
+ Version: 0.1.0
4
+ Summary: Experimental toolkit for reviewing candidate links between conflict incident records
5
+ Author: Fabian Figueroa
6
+ License-Expression: MIT
7
+ Project-URL: Repository, https://github.com/FabianFig/ccrt
8
+ Project-URL: Issues, https://github.com/FabianFig/ccrt/issues
9
+ Keywords: conflict,record-linkage,ucdp,acled,research
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Operating System :: OS Independent
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: httpx<1,>=0.27
23
+ Requires-Dist: python-dotenv<2,>=1
24
+ Provides-Extra: tables
25
+ Requires-Dist: pandas>=2.2; extra == "tables"
26
+ Requires-Dist: pyarrow>=15; extra == "tables"
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=8; extra == "dev"
29
+ Requires-Dist: ruff>=0.9; extra == "dev"
30
+ Requires-Dist: build>=1.2; extra == "dev"
31
+ Requires-Dist: twine>=6; extra == "dev"
32
+ Requires-Dist: nbformat>=5.10; extra == "dev"
33
+ Requires-Dist: nbclient>=0.10; extra == "dev"
34
+ Requires-Dist: ipykernel>=6; extra == "dev"
35
+ Dynamic: license-file
36
+
37
+ # Conflict Casualty Reconciliation Toolkit (CCRT)
38
+
39
+ CCRT compares source accounts of conflict incidents. It imports UCDP GED and ACLED
40
+ records, proposes possible links, supports human review, and exports results while
41
+ preserving each source's original claims.
42
+
43
+ **Status:** `0.1.0` is an experimental research library.
44
+
45
+ ## Installation
46
+
47
+ CCRT supports Python 3.10–3.14. To install a published release:
48
+
49
+ ```bash
50
+ python -m pip install ccrt
51
+ ```
52
+
53
+ Install `ccrt[tables]` for pandas and Parquet support. From a checkout, use
54
+ `python -m pip install -e .`, or `python -m pip install -e '.[tables,dev]'` for the
55
+ project's development checks.
56
+
57
+ ## Quickstart
58
+
59
+ The example files contain invented incidents in Exampleland. From the repository
60
+ root, with an unused `work/` directory:
61
+
62
+ ```bash
63
+ mkdir -p work
64
+ ccrt import ucdp examples/ucdp_synthetic.csv work/ucdp.json --version 26.1
65
+ ccrt import acled examples/acled_synthetic.csv work/acled.json --version synthetic-2026-09-24
66
+ ccrt match work/ucdp.json work/acled.json work/matches
67
+ ccrt report work/matches/run.json --review work/matches/review.json
68
+ ```
69
+
70
+ The match run contains three candidate links and four unlinked source records.
71
+ Inspect `work/matches/candidates.csv`, then use the [usage guide](docs/usage.md) to
72
+ record decisions and export reviewed results. CCRT does not overwrite existing
73
+ outputs.
74
+
75
+ ## Python API
76
+
77
+ ```python
78
+ from ccrt import MatchConfig, load_acled_csv, load_ucdp_csv, reconcile
79
+
80
+ ucdp = load_ucdp_csv("examples/ucdp_synthetic.csv", version="26.1")
81
+ acled = load_acled_csv("examples/acled_synthetic.csv", version="synthetic-2026-09-24")
82
+ result = reconcile(ucdp, acled, config=MatchConfig(max_days=3, max_km=25))
83
+
84
+ for candidate in result.candidates:
85
+ print(candidate.left.source_id, candidate.right.source_id, candidate.score)
86
+ ```
87
+
88
+ The [API reference](docs/api.md) covers review decisions, saved runs, reports, and
89
+ exports.
90
+
91
+ ## Sources and capabilities
92
+
93
+ - UCDP GED 25.1 and 26.1 CSV files with modern field names, and authenticated GED
94
+ API access.
95
+ - ACLED event CSV exports with current lowercase field names. The supplied version
96
+ is a label for the exact export snapshot.
97
+ - Source-preserving JSON bundles, candidate generation, CSV and terminal review,
98
+ review reports, CSV export, and optional Parquet export.
99
+
100
+ See [source mappings](docs/source-mappings.md) for supported fields, precision
101
+ handling, and provenance. The [teaching notebook](examples/reconciliation_walkthrough.ipynb)
102
+ provides another offline example.
103
+
104
+ ## Limitations
105
+
106
+ CCRT compares source accounts; it does not establish objective incident identity.
107
+ Candidate scores are uncalibrated proximity rankings, and generating a candidate
108
+ does not confirm correspondence. Real-world linkage accuracy has not been
109
+ independently evaluated. CCRT does not reconcile casualty totals or estimate
110
+ underreporting. See [validation notes](docs/validation.md) for software checks and
111
+ research limits.
112
+
113
+ ## Contributing and licence
114
+
115
+ See [CONTRIBUTING.md](CONTRIBUTING.md), the [changelog](CHANGELOG.md), and the
116
+ [release procedure](docs/releasing.md). The code and synthetic examples are under
117
+ the [MIT licence](LICENSE). UCDP and ACLED data have separate access, attribution,
118
+ and redistribution terms; obtain source access and check the terms for your snapshot.
ccrt-0.1.0/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # Conflict Casualty Reconciliation Toolkit (CCRT)
2
+
3
+ CCRT compares source accounts of conflict incidents. It imports UCDP GED and ACLED
4
+ records, proposes possible links, supports human review, and exports results while
5
+ preserving each source's original claims.
6
+
7
+ **Status:** `0.1.0` is an experimental research library.
8
+
9
+ ## Installation
10
+
11
+ CCRT supports Python 3.10–3.14. To install a published release:
12
+
13
+ ```bash
14
+ python -m pip install ccrt
15
+ ```
16
+
17
+ Install `ccrt[tables]` for pandas and Parquet support. From a checkout, use
18
+ `python -m pip install -e .`, or `python -m pip install -e '.[tables,dev]'` for the
19
+ project's development checks.
20
+
21
+ ## Quickstart
22
+
23
+ The example files contain invented incidents in Exampleland. From the repository
24
+ root, with an unused `work/` directory:
25
+
26
+ ```bash
27
+ mkdir -p work
28
+ ccrt import ucdp examples/ucdp_synthetic.csv work/ucdp.json --version 26.1
29
+ ccrt import acled examples/acled_synthetic.csv work/acled.json --version synthetic-2026-09-24
30
+ ccrt match work/ucdp.json work/acled.json work/matches
31
+ ccrt report work/matches/run.json --review work/matches/review.json
32
+ ```
33
+
34
+ The match run contains three candidate links and four unlinked source records.
35
+ Inspect `work/matches/candidates.csv`, then use the [usage guide](docs/usage.md) to
36
+ record decisions and export reviewed results. CCRT does not overwrite existing
37
+ outputs.
38
+
39
+ ## Python API
40
+
41
+ ```python
42
+ from ccrt import MatchConfig, load_acled_csv, load_ucdp_csv, reconcile
43
+
44
+ ucdp = load_ucdp_csv("examples/ucdp_synthetic.csv", version="26.1")
45
+ acled = load_acled_csv("examples/acled_synthetic.csv", version="synthetic-2026-09-24")
46
+ result = reconcile(ucdp, acled, config=MatchConfig(max_days=3, max_km=25))
47
+
48
+ for candidate in result.candidates:
49
+ print(candidate.left.source_id, candidate.right.source_id, candidate.score)
50
+ ```
51
+
52
+ The [API reference](docs/api.md) covers review decisions, saved runs, reports, and
53
+ exports.
54
+
55
+ ## Sources and capabilities
56
+
57
+ - UCDP GED 25.1 and 26.1 CSV files with modern field names, and authenticated GED
58
+ API access.
59
+ - ACLED event CSV exports with current lowercase field names. The supplied version
60
+ is a label for the exact export snapshot.
61
+ - Source-preserving JSON bundles, candidate generation, CSV and terminal review,
62
+ review reports, CSV export, and optional Parquet export.
63
+
64
+ See [source mappings](docs/source-mappings.md) for supported fields, precision
65
+ handling, and provenance. The [teaching notebook](examples/reconciliation_walkthrough.ipynb)
66
+ provides another offline example.
67
+
68
+ ## Limitations
69
+
70
+ CCRT compares source accounts; it does not establish objective incident identity.
71
+ Candidate scores are uncalibrated proximity rankings, and generating a candidate
72
+ does not confirm correspondence. Real-world linkage accuracy has not been
73
+ independently evaluated. CCRT does not reconcile casualty totals or estimate
74
+ underreporting. See [validation notes](docs/validation.md) for software checks and
75
+ research limits.
76
+
77
+ ## Contributing and licence
78
+
79
+ See [CONTRIBUTING.md](CONTRIBUTING.md), the [changelog](CHANGELOG.md), and the
80
+ [release procedure](docs/releasing.md). The code and synthetic examples are under
81
+ the [MIT licence](LICENSE). UCDP and ACLED data have separate access, attribution,
82
+ and redistribution terms; obtain source access and check the terms for your snapshot.
@@ -0,0 +1,57 @@
1
+ """Reproducible synthetic throughput baseline. Run after installing CCRT."""
2
+
3
+ import argparse
4
+ import json
5
+ import platform
6
+ import time
7
+ import tracemalloc
8
+ from datetime import date, timedelta
9
+
10
+ from ccrt import Dataset, MatchConfig, SourceRecord, reconcile
11
+ from ccrt.schema import digest
12
+
13
+
14
+ def run(count):
15
+ start = date(2000, 1, 1)
16
+ datasets = []
17
+ for source in ("left", "right"):
18
+ records = tuple(
19
+ SourceRecord(
20
+ source,
21
+ str(i),
22
+ "synthetic-v1",
23
+ start + timedelta(days=i % 3650),
24
+ start + timedelta(days=i % 3650),
25
+ latitude=(i % 90) - 45,
26
+ longitude=(i % 180) - 90,
27
+ country="Exampleland",
28
+ date_precision=1,
29
+ geo_precision=1,
30
+ )
31
+ for i in range(count)
32
+ )
33
+ datasets.append(Dataset(source, "synthetic-v1", records, digest([r.to_dict() for r in records])))
34
+ tracemalloc.start()
35
+ began = time.perf_counter()
36
+ result = reconcile(*datasets, config=MatchConfig(max_days=1, max_km=5))
37
+ elapsed = time.perf_counter() - began
38
+ _, peak = tracemalloc.get_traced_memory()
39
+ tracemalloc.stop()
40
+ return {
41
+ "records_per_source": count,
42
+ "candidates": len(result.candidates),
43
+ "elapsed_seconds": round(elapsed, 3),
44
+ "peak_python_mib": round(peak / 1024**2, 2),
45
+ "python": platform.python_version(),
46
+ "platform": platform.platform(),
47
+ "distribution": "3650 dates, repeated deterministic coordinates; max_days=1, max_km=5",
48
+ }
49
+
50
+
51
+ if __name__ == "__main__":
52
+ parser = argparse.ArgumentParser()
53
+ parser.add_argument("--records", type=int, default=10000)
54
+ args = parser.parse_args()
55
+ if not 1 <= args.records <= 100000:
56
+ parser.error("--records must be between 1 and 100000")
57
+ print(json.dumps(run(args.records), indent=2))
@@ -0,0 +1,42 @@
1
+ """Sensitivity analysis on exhaustively labeled, bounded inputs."""
2
+
3
+ import argparse
4
+ import csv
5
+ import json
6
+ from dataclasses import asdict
7
+
8
+ from ccrt import MatchConfig, load_dataset, reconcile
9
+ from ccrt.evaluation import evaluate_candidates
10
+
11
+
12
+ def main():
13
+ parser = argparse.ArgumentParser()
14
+ parser.add_argument("left")
15
+ parser.add_argument("right")
16
+ parser.add_argument("truth_csv", help="All true pairs: left_id,right_id")
17
+ parser.add_argument(
18
+ "--exhaustive",
19
+ action="store_true",
20
+ required=True,
21
+ help="Acknowledge that unlabeled pairs in these inputs are negative",
22
+ )
23
+ args = parser.parse_args()
24
+ with open(args.truth_csv, newline="", encoding="utf-8") as stream:
25
+ truth = {(row["left_id"], row["right_id"]) for row in csv.DictReader(stream)}
26
+ left, right = load_dataset(args.left), load_dataset(args.right)
27
+ reports = []
28
+ for max_days, max_km, coarse in ((0, 1, False), (3, 25, False), (3, 25, True)):
29
+ config = MatchConfig(max_days=max_days, max_km=max_km, include_coarse_locations=coarse)
30
+ result = reconcile(left, right, config=config)
31
+ reports.append(
32
+ {
33
+ "config": config.to_dict(),
34
+ "run_id": result.run_id,
35
+ **asdict(evaluate_candidates(result, truth)),
36
+ }
37
+ )
38
+ print(json.dumps(reports, indent=2))
39
+
40
+
41
+ if __name__ == "__main__":
42
+ main()
@@ -0,0 +1,124 @@
1
+ """Exercise an installed wheel outside its checkout, with optional table support.
2
+
3
+ Run using a fresh environment's Python from a directory outside this repository:
4
+ /tmp/ccrt-smoke/bin/python /path/to/repo/benchmarks/smoke_install.py
5
+ Then install the wheel's tables extra and repeat with --tables.
6
+ """
7
+
8
+ import argparse
9
+ import csv
10
+ import importlib.util
11
+ import json
12
+ import os
13
+ import subprocess
14
+ import sys
15
+ import tempfile
16
+ from pathlib import Path
17
+
18
+ import ccrt
19
+
20
+
21
+ def main():
22
+ parser = argparse.ArgumentParser(description=__doc__)
23
+ parser.add_argument("--tables", action="store_true")
24
+ args = parser.parse_args()
25
+ checkout = Path(__file__).resolve().parents[1]
26
+ installed = Path(ccrt.__file__).resolve()
27
+ if installed.is_relative_to(checkout):
28
+ raise RuntimeError("smoke test imported the checkout; use a fresh wheel installation")
29
+ if not args.tables:
30
+ assert importlib.util.find_spec("pandas") is None, "core smoke environment contains pandas"
31
+ assert importlib.util.find_spec("pyarrow") is None, "core smoke environment contains PyArrow"
32
+ console = Path(sys.executable).parent / ("ccrt.exe" if os.name == "nt" else "ccrt")
33
+ child_env = dict(os.environ)
34
+ child_env.pop("PYTHONPATH", None)
35
+
36
+ with tempfile.TemporaryDirectory(prefix="ccrt-installed-workflow-") as directory:
37
+ work = Path(directory)
38
+
39
+ def run(*arguments):
40
+ completed = subprocess.run(
41
+ [str(console), *map(str, arguments)],
42
+ cwd=work,
43
+ env=child_env,
44
+ capture_output=True,
45
+ text=True,
46
+ )
47
+ if completed.returncode:
48
+ raise RuntimeError(f"ccrt {' '.join(map(str, arguments))}: {completed.stderr}")
49
+ return completed.stdout
50
+
51
+ assert "Reviewable" in run("--help")
52
+ assert run("--version").strip() == ccrt.__version__
53
+ run(
54
+ "import",
55
+ "ucdp",
56
+ checkout / "examples/ucdp_synthetic.csv",
57
+ work / "ucdp.json",
58
+ "--version",
59
+ "26.1",
60
+ )
61
+ run(
62
+ "import",
63
+ "acled",
64
+ checkout / "examples/acled_synthetic.csv",
65
+ work / "acled.json",
66
+ "--version",
67
+ "smoke",
68
+ )
69
+ run("match", work / "ucdp.json", work / "acled.json", work / "matches")
70
+ result = ccrt.load_run(work / "matches/run.json")
71
+ assert len(result.candidates) == 3
72
+ candidate = next(
73
+ c for c in result.candidates if (c.left.source_id, c.right.source_id) == ("U1", "A1")
74
+ )
75
+ run(
76
+ "review",
77
+ work / "matches/run.json",
78
+ work / "review.json",
79
+ "--candidate",
80
+ candidate.candidate_id,
81
+ "--status",
82
+ "accepted",
83
+ "--reviewer",
84
+ "Smoke test",
85
+ )
86
+ run(
87
+ "report",
88
+ work / "matches/run.json",
89
+ "--review",
90
+ work / "review.json",
91
+ "--output",
92
+ work / "report.json",
93
+ )
94
+ report = json.loads((work / "report.json").read_text())
95
+ assert (report["accepted_count"], report["unresolved_count"]) == (1, 2)
96
+ run(
97
+ "export",
98
+ work / "matches/run.json",
99
+ work / "review.csv",
100
+ "--format",
101
+ "review-csv",
102
+ "--review",
103
+ work / "review.json",
104
+ )
105
+ with (work / "review.csv").open(newline="", encoding="utf-8") as stream:
106
+ rows = list(csv.DictReader(stream))
107
+ assert sum(row["status"] == "accepted" for row in rows) == 1
108
+ assert sum(row["status"] == "unlinked" for row in rows) == 4
109
+ run("export", work / "ucdp.json", work / "records.csv", "--format", "csv")
110
+ if args.tables:
111
+ from ccrt.export import read_parquet
112
+
113
+ run("export", work / "ucdp.json", work / "ucdp.parquet", "--format", "parquet")
114
+ original = ccrt.load_dataset(work / "ucdp.json")
115
+ assert read_parquet(work / "ucdp.parquet") == original
116
+ assert len(original.to_pandas()) == len(original)
117
+
118
+ subprocess.run([sys.executable, "-m", "pip", "check"], check=True, env=child_env)
119
+ mode = "core + tables" if args.tables else "core"
120
+ print(f"Installed-package workflow passed ({mode}): {installed}")
121
+
122
+
123
+ if __name__ == "__main__":
124
+ main()
@@ -0,0 +1,37 @@
1
+ """Public CCRT API. Source accounts stay separate from proposed event links."""
2
+
3
+ from ccrt._version import __version__
4
+ from ccrt.client import UCDPClient, UCDPError
5
+ from ccrt.loaders import ValidationError, load_acled_csv, load_csv, load_ucdp_csv
6
+ from ccrt.merge import Candidate, MatchConfig, Reconciliation, reconcile
7
+ from ccrt.report import ReviewReport, summarize_review
8
+ from ccrt.review import Decision, Review
9
+ from ccrt.schema import Dataset, SourceRecord, ValidationIssue
10
+ from ccrt.storage import load_dataset, load_review, load_run, save_dataset, save_review, save_run
11
+
12
+ __all__ = [
13
+ "__version__",
14
+ "SourceRecord",
15
+ "Dataset",
16
+ "ValidationIssue",
17
+ "ValidationError",
18
+ "UCDPClient",
19
+ "UCDPError",
20
+ "load_ucdp_csv",
21
+ "load_acled_csv",
22
+ "load_csv",
23
+ "MatchConfig",
24
+ "Candidate",
25
+ "Reconciliation",
26
+ "reconcile",
27
+ "Decision",
28
+ "Review",
29
+ "ReviewReport",
30
+ "summarize_review",
31
+ "load_dataset",
32
+ "save_dataset",
33
+ "load_run",
34
+ "save_run",
35
+ "load_review",
36
+ "save_review",
37
+ ]
@@ -0,0 +1,3 @@
1
+ from ccrt.cli import main
2
+
3
+ raise SystemExit(main())
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"