crossref-matcher 1.2.2__tar.gz → 1.3.1__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.
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/MANIFEST.in +1 -0
- {crossref_matcher-1.2.2/crossref_matcher.egg-info → crossref_matcher-1.3.1}/PKG-INFO +1 -1
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/README.md +43 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/evaluation/evaluation.py +21 -7
- crossref_matcher-1.3.1/crossref_matcher/evaluation/sparse_metrics_verification/verify_sparse_metrics.py +239 -0
- crossref_matcher-1.3.1/crossref_matcher/resources/VERSION.txt +1 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/__init__.py +25 -5
- crossref_matcher-1.3.1/crossref_matcher/strategies/funder/funder_name_to_ror_search/version.txt +1 -0
- crossref_matcher-1.3.1/crossref_matcher/strategies/reference/sbmv_unstructured/__init__.py +0 -0
- crossref_matcher-1.3.1/crossref_matcher/strategies/versioning.py +77 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1/crossref_matcher.egg-info}/PKG-INFO +1 -1
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher.egg-info/SOURCES.txt +4 -0
- crossref_matcher-1.2.2/crossref_matcher/resources/VERSION.txt +0 -1
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/LICENSE +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/MatchTask.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/__init__.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/app.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/evaluation/__init__.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/evaluation/all_links.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/evaluation/api_routes.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/evaluation/dataset_ingest.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/evaluation/google_sheets_connector.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/evaluation/ror_utils.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/evaluation/schemas.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/indexes → crossref_matcher-1.3.1/crossref_matcher/evaluation/sparse_metrics_verification}/__init__.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/indexes/preprint_matching → crossref_matcher-1.3.1/crossref_matcher/indexes}/__init__.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/indexes/ror_organizations → crossref_matcher-1.3.1/crossref_matcher/indexes/preprint_matching}/__init__.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/indexes/preprint_matching/indexer_service.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/matching → crossref_matcher-1.3.1/crossref_matcher/indexes/ror_organizations}/__init__.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/indexes/ror_organizations/create_index.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/indexes/ror_organizations/index_data.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/indexes/ror_organizations/ror_opensearch_update_dag.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/indexes/ror_organizations/ror_zenodo_api.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/resources → crossref_matcher-1.3.1/crossref_matcher/matching}/__init__.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/matching/http_utils.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/matching/run_strategy_on_dataset.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/matching/schemas.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/matching/search.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/matching/utils.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/resources/affiliation → crossref_matcher-1.3.1/crossref_matcher/resources}/__init__.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/resources/data → crossref_matcher-1.3.1/crossref_matcher/resources/affiliation}/__init__.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/resources/data/common_ror_endings → crossref_matcher-1.3.1/crossref_matcher/resources/data}/__init__.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/resources/data/countries → crossref_matcher-1.3.1/crossref_matcher/resources/data/common_ror_endings}/__init__.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/resources/data/common_ror_endings/common_ror_endings.txt +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/resources/data/datasets → crossref_matcher-1.3.1/crossref_matcher/resources/data/countries}/__init__.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/resources/data/countries/china_placenames.txt +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/resources/data/countries/countries.txt +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/resources/data/countries/countries2.txt +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/resources/data/models → crossref_matcher-1.3.1/crossref_matcher/resources/data/datasets}/__init__.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/resources/data/models/preprint_ensemble_ml → crossref_matcher-1.3.1/crossref_matcher/resources/data/models}/__init__.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/resources/data/models/reference_nn_unstructured → crossref_matcher-1.3.1/crossref_matcher/resources/data/models/preprint_ensemble_ml}/__init__.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/resources/data/models/preprint_ensemble_ml/data_counters0.pkl +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/resources/data/models/preprint_ensemble_ml/eval/events.out.tfevents.1690572611.knopfler.local +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/resources/data/models/preprint_ensemble_ml/model.opt_slots0.npy.gz +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/resources/data/models/preprint_ensemble_ml/model.pkl.gz +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/resources/data/models/preprint_ensemble_ml/model.weights.npy.gz +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/resources/data/models/preprint_ensemble_ml/train/events.out.tfevents.1690572611.knopfler.local +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/resources/data/models/preprint_ml/data_counters0.pkl +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/resources/data/models/preprint_ml/eval/events.out.tfevents.1690302892.knopfler.local +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/resources/data/models/preprint_ml/model.opt_slots0.npy.gz +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/resources/data/models/preprint_ml/model.pkl.gz +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/resources/data/models/preprint_ml/model.weights.npy.gz +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/resources/data/models/preprint_ml/train/events.out.tfevents.1690302891.knopfler.local +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/strategies/affiliation → crossref_matcher-1.3.1/crossref_matcher/resources/data/models/reference_nn_unstructured}/__init__.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/resources/data/models/reference_nn_unstructured/data_counters0.pkl +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/resources/data/models/reference_nn_unstructured/eval/events.out.tfevents.1681511550.knopfler.local +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/resources/data/models/reference_nn_unstructured/model.opt_slots0.npy.gz +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/resources/data/models/reference_nn_unstructured/model.pkl.gz +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/resources/data/models/reference_nn_unstructured/model.weights.npy.gz +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/resources/data/models/reference_nn_unstructured/train/events.out.tfevents.1681511550.knopfler.local +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/resources/plugins.json +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/run_plugin_test.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/strategies/affiliation/multi_search → crossref_matcher-1.3.1/crossref_matcher/strategies/affiliation}/__init__.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/affiliation/es_utils.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/affiliation/matching.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/strategies/affiliation/single_search → crossref_matcher-1.3.1/crossref_matcher/strategies/affiliation/multi_search}/__init__.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/affiliation/multi_search/strategy.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/strategies/funder → crossref_matcher-1.3.1/crossref_matcher/strategies/affiliation/single_search}/__init__.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/affiliation/single_search/strategy.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/affiliation/strategy.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/common.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/strategies/funder/funder_name_to_ror_search → crossref_matcher-1.3.1/crossref_matcher/strategies/funder}/__init__.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/strategies/healthcheck → crossref_matcher-1.3.1/crossref_matcher/strategies/funder/funder_name_to_ror_search}/__init__.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/funder/funder_name_to_ror_search/strategy.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/strategies/healthcheck → crossref_matcher-1.3.1/crossref_matcher/strategies}/healthcheck/__init__.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/strategies/preprint → crossref_matcher-1.3.1/crossref_matcher/strategies/healthcheck/healthcheck}/__init__.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/healthcheck/healthcheck/strategy.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/strategies/preprint/ensemble_avg → crossref_matcher-1.3.1/crossref_matcher/strategies/preprint}/__init__.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/strategies/preprint/ensemble_cascade → crossref_matcher-1.3.1/crossref_matcher/strategies/preprint/ensemble_avg}/__init__.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/preprint/ensemble_avg/strategy.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/strategies/preprint/ensemble_ml → crossref_matcher-1.3.1/crossref_matcher/strategies/preprint/ensemble_cascade}/__init__.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/preprint/ensemble_cascade/strategy.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/strategies/preprint/preprint_ml → crossref_matcher-1.3.1/crossref_matcher/strategies/preprint/ensemble_ml}/__init__.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/preprint/ensemble_ml/get_train_data.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/preprint/ensemble_ml/strategy.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/preprint/ensemble_ml/train.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/preprint/ensemble_ml/utils.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/strategies/preprint/sbmv → crossref_matcher-1.3.1/crossref_matcher/strategies/preprint/preprint_ml}/__init__.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/preprint/preprint_ml/get_train_data.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/preprint/preprint_ml/strategy.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/preprint/preprint_ml/train.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/preprint/preprint_ml/utils.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/strategies/preprint/sbmv_es → crossref_matcher-1.3.1/crossref_matcher/strategies/preprint/sbmv}/__init__.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/preprint/sbmv/strategy.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/strategies/preprint/sbmv_rels → crossref_matcher-1.3.1/crossref_matcher/strategies/preprint/sbmv_es}/__init__.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/preprint/sbmv_es/strategy.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/strategies/reference → crossref_matcher-1.3.1/crossref_matcher/strategies/preprint/sbmv_rels}/__init__.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/preprint/sbmv_rels/strategy.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/strategies/reference/cs_combined → crossref_matcher-1.3.1/crossref_matcher/strategies/reference}/__init__.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/strategies/reference/nn_unstructured → crossref_matcher-1.3.1/crossref_matcher/strategies/reference/cs_combined}/__init__.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/reference/cs_combined/strategy.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/strategies/reference/parsed_doi → crossref_matcher-1.3.1/crossref_matcher/strategies/reference/nn_unstructured}/__init__.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/reference/nn_unstructured/get_train_data.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/reference/nn_unstructured/strategy.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/reference/nn_unstructured/train.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/reference/nn_unstructured/utils.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/strategies/reference/parsed_isbn → crossref_matcher-1.3.1/crossref_matcher/strategies/reference/parsed_doi}/__init__.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/reference/parsed_doi/strategy.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/strategies/reference/sbmv → crossref_matcher-1.3.1/crossref_matcher/strategies/reference/parsed_isbn}/__init__.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/reference/parsed_isbn/strategy.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/strategies/reference/sbmv_orig → crossref_matcher-1.3.1/crossref_matcher/strategies/reference/sbmv}/__init__.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/reference/sbmv/strategy.py +0 -0
- {crossref_matcher-1.2.2/crossref_matcher/strategies/reference/sbmv_unstructured → crossref_matcher-1.3.1/crossref_matcher/strategies/reference/sbmv_orig}/__init__.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/reference/sbmv_orig/strategy.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/strategies/reference/sbmv_unstructured/strategy.py +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher.egg-info/dependency_links.txt +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher.egg-info/entry_points.txt +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher.egg-info/requires.txt +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher.egg-info/top_level.txt +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/setup.cfg +0 -0
- {crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/setup.py +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
include crossref_matcher/resources/plugins.json
|
|
2
2
|
include crossref_matcher/resources/VERSION.txt
|
|
3
|
+
recursive-include crossref_matcher/strategies *.txt
|
|
3
4
|
recursive-include crossref_matcher/resources/data/models *.pkl *.gz *.local
|
|
4
5
|
recursive-include crossref_matcher/resources/data/common_ror_endings *.txt
|
|
5
6
|
recursive-include crossref_matcher/resources/data/countries *.txt
|
|
@@ -135,6 +135,49 @@ Strategies are located in the [strategies directory](crossref_matcher/strategies
|
|
|
135
135
|
|
|
136
136
|
Strategies must be added to the "enabled" list in [plugins.json](crossref_matcher/resources/plugins.json) before they can be used.
|
|
137
137
|
|
|
138
|
+
### Strategy versioning
|
|
139
|
+
|
|
140
|
+
Each strategy directory can contain a `version.txt` file with a two-part version number (`MAJOR.MINOR`). This versioning is independent of the project's semver.
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
crossref_matcher/strategies/preprint/sbmv/
|
|
144
|
+
├── strategy.py
|
|
145
|
+
└── version.txt # contains e.g. "1.2"
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
When a strategy is loaded, its version is automatically available as a class attribute:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from crossref_matcher.strategies.preprint.sbmv.strategy import Sbmv
|
|
152
|
+
print(Sbmv.version) # "1.2"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Strategies without a `version.txt` have `version = None`.
|
|
156
|
+
|
|
157
|
+
**Bumping a version:**
|
|
158
|
+
|
|
159
|
+
```sh
|
|
160
|
+
python scripts/bump_strategy_version.py preprint/sbmv minor # 1.2 → 1.3
|
|
161
|
+
python scripts/bump_strategy_version.py preprint/sbmv major # 1.2 → 2.0
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The path argument can be relative to the `strategies/` directory or an absolute path.
|
|
165
|
+
|
|
166
|
+
**CI enforcement:**
|
|
167
|
+
|
|
168
|
+
A CI job (`check-strategy-versions` in `.gitlab-ci.yml`) fails any merge request where a strategy's files are changed but `version.txt` is not updated. To bypass for a trivial change (e.g. a typo fix):
|
|
169
|
+
|
|
170
|
+
- Pass `--force` to the check script, or
|
|
171
|
+
- Set the `SKIP_BUMP_CHECK=1` environment variable on the pipeline run.
|
|
172
|
+
|
|
173
|
+
**Local pre-commit hook:**
|
|
174
|
+
|
|
175
|
+
To enable the check locally (as a non-blocking warning):
|
|
176
|
+
|
|
177
|
+
```sh
|
|
178
|
+
git config core.hooksPath .githooks
|
|
179
|
+
```
|
|
180
|
+
|
|
138
181
|
### Dependencies
|
|
139
182
|
|
|
140
183
|
Requirements are defined in `setup.py`. Requirements files with exact versions can be updated with `pip-compile`, which is a part of `pip-tools`. First run `pip install pip-tools`. Then:
|
|
@@ -176,15 +176,29 @@ class EvaluateSklearn:
|
|
|
176
176
|
self.mlb.fit(y_true + y_pred)
|
|
177
177
|
y_true_transformed = self.mlb.transform(y_true)
|
|
178
178
|
y_pred_transformed = self.mlb.transform(y_pred)
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
179
|
+
# Stay in sparse representation: with large label spaces (e.g. grant
|
|
180
|
+
# IDs), densifying via .toarray() blows up memory/time since the
|
|
181
|
+
# dense matrix is n_samples x n_unique_labels.
|
|
182
|
+
true_positives = y_true_transformed.multiply(y_pred_transformed).sum()
|
|
183
|
+
false_positives = int(y_pred_transformed.sum() - true_positives)
|
|
184
|
+
false_negatives = int(y_true_transformed.sum() - true_positives)
|
|
185
|
+
# sklearn's type_of_target() requires >1 column to classify a target
|
|
186
|
+
# as "multilabel-indicator" (native sparse support); with exactly one
|
|
187
|
+
# label column it's classified as "binary" instead, which sklearn
|
|
188
|
+
# handles via column_or_1d(), and that rejects sparse input outright.
|
|
189
|
+
# Densify just for that narrow edge case (cheap, since there's only
|
|
190
|
+
# one column).
|
|
191
|
+
if y_true_transformed.shape[1] <= 1:
|
|
192
|
+
y_true_for_fscore = y_true_transformed.toarray()
|
|
193
|
+
y_pred_for_fscore = y_pred_transformed.toarray()
|
|
194
|
+
else:
|
|
195
|
+
y_true_for_fscore = y_true_transformed
|
|
196
|
+
y_pred_for_fscore = y_pred_transformed
|
|
183
197
|
f_scores = []
|
|
184
198
|
for beta in f_score_betas:
|
|
185
199
|
precision, recall, fscore, _ = precision_recall_fscore_support(
|
|
186
|
-
|
|
187
|
-
|
|
200
|
+
y_true_for_fscore,
|
|
201
|
+
y_pred_for_fscore,
|
|
188
202
|
average="micro",
|
|
189
203
|
zero_division=0.0,
|
|
190
204
|
sample_weight=sample_weights,
|
|
@@ -352,7 +366,7 @@ def run_strategy_on_eval_data(
|
|
|
352
366
|
extra_data = extra_fn(strategy, data_point, this_result)
|
|
353
367
|
out_item["extra"].update(extra_data)
|
|
354
368
|
|
|
355
|
-
if hasattr(data_point, "alternates"):
|
|
369
|
+
if hasattr(data_point, "alternates") and len(data_point.alternates) > 0:
|
|
356
370
|
dataset_has_alternates = True
|
|
357
371
|
out_item["alternates"] = data_point.alternates
|
|
358
372
|
this_weight = getattr(data_point, "weight", 1.0)
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""Reproduces the investigation behind the sparse-matrix rewrite of
|
|
2
|
+
`EvaluateSklearn.run_one_evaluation` in `crossref_matcher/evaluation/evaluation.py`.
|
|
3
|
+
|
|
4
|
+
Run directly to reproduce all findings:
|
|
5
|
+
|
|
6
|
+
python -m crossref_matcher.evaluation.sparse_metrics_verification.verify_sparse_metrics
|
|
7
|
+
|
|
8
|
+
See README.md in this directory for the narrative writeup of what each
|
|
9
|
+
check demonstrates.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import random
|
|
13
|
+
import time
|
|
14
|
+
import tracemalloc
|
|
15
|
+
|
|
16
|
+
import numpy as np
|
|
17
|
+
from sklearn.metrics import precision_recall_fscore_support
|
|
18
|
+
from sklearn.preprocessing import MultiLabelBinarizer
|
|
19
|
+
from sklearn.utils.multiclass import type_of_target
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def old_dense_false_positives_negatives(at_sparse, af_sparse):
|
|
23
|
+
"""The original implementation: densify, then bitwise AND."""
|
|
24
|
+
at = at_sparse.toarray()
|
|
25
|
+
af = af_sparse.toarray()
|
|
26
|
+
false_positives = int((af & (1 - at)).sum())
|
|
27
|
+
false_negatives = int((at & (1 - af)).sum())
|
|
28
|
+
return false_positives, false_negatives
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def new_sparse_false_positives_negatives(at_sparse, af_sparse):
|
|
32
|
+
"""The replacement implementation: stay sparse throughout."""
|
|
33
|
+
true_positives = at_sparse.multiply(af_sparse).sum()
|
|
34
|
+
false_positives = int(af_sparse.sum() - true_positives)
|
|
35
|
+
false_negatives = int(at_sparse.sum() - true_positives)
|
|
36
|
+
return false_positives, false_negatives
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def fscore_inputs(at_sparse, af_sparse):
|
|
40
|
+
"""Mirrors the shape[1] <= 1 densify branch added to run_one_evaluation."""
|
|
41
|
+
if at_sparse.shape[1] <= 1:
|
|
42
|
+
return at_sparse.toarray(), af_sparse.toarray()
|
|
43
|
+
return at_sparse, af_sparse
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def check_equivalence(trials: int = 500, seed: int = 42) -> int:
|
|
47
|
+
"""Fuzz-tests old (dense bitwise) vs. new (sparse) false_positives /
|
|
48
|
+
false_negatives / precision / recall / f-score against each other over
|
|
49
|
+
randomized multi-label data, including empty label sets and duplicate
|
|
50
|
+
labels within a single sample.
|
|
51
|
+
|
|
52
|
+
Returns the number of mismatches found (0 means full equivalence).
|
|
53
|
+
"""
|
|
54
|
+
rng = random.Random(seed)
|
|
55
|
+
mismatches = 0
|
|
56
|
+
checked = 0
|
|
57
|
+
|
|
58
|
+
for trial in range(trials):
|
|
59
|
+
n_samples = rng.randint(1, 8)
|
|
60
|
+
vocab = [f"id{i}" for i in range(rng.randint(1, 6))]
|
|
61
|
+
y_true, y_pred = [], []
|
|
62
|
+
for _ in range(n_samples):
|
|
63
|
+
yt = rng.sample(vocab, k=rng.randint(0, len(vocab)))
|
|
64
|
+
if yt and rng.random() < 0.3:
|
|
65
|
+
yt = yt + [yt[0]] # duplicate label within a sample
|
|
66
|
+
yp = rng.sample(vocab, k=rng.randint(0, len(vocab)))
|
|
67
|
+
y_true.append(yt)
|
|
68
|
+
y_pred.append(yp)
|
|
69
|
+
|
|
70
|
+
if not any(y_true) and not any(y_pred):
|
|
71
|
+
continue # matches the early-return "all empty" branch in evaluation.py
|
|
72
|
+
|
|
73
|
+
mlb = MultiLabelBinarizer(sparse_output=True)
|
|
74
|
+
mlb.fit(y_true + y_pred)
|
|
75
|
+
at_sparse = mlb.transform(y_true)
|
|
76
|
+
af_sparse = mlb.transform(y_pred)
|
|
77
|
+
|
|
78
|
+
checked += 1
|
|
79
|
+
|
|
80
|
+
fp_old, fn_old = old_dense_false_positives_negatives(at_sparse, af_sparse)
|
|
81
|
+
fp_new, fn_new = new_sparse_false_positives_negatives(at_sparse, af_sparse)
|
|
82
|
+
if (fp_old, fn_old) != (fp_new, fn_new):
|
|
83
|
+
mismatches += 1
|
|
84
|
+
print(f" MISMATCH (fp/fn) trial={trial} y_true={y_true} y_pred={y_pred}")
|
|
85
|
+
print(f" old: fp={fp_old} fn={fn_old} new: fp={fp_new} fn={fn_new}")
|
|
86
|
+
|
|
87
|
+
at_dense, af_dense = at_sparse.toarray(), af_sparse.toarray()
|
|
88
|
+
p_old, r_old, f_old, _ = precision_recall_fscore_support(
|
|
89
|
+
at_dense, af_dense, average="micro", zero_division=0.0, beta=1.0
|
|
90
|
+
)
|
|
91
|
+
yt_new, yp_new = fscore_inputs(at_sparse, af_sparse)
|
|
92
|
+
p_new, r_new, f_new, _ = precision_recall_fscore_support(
|
|
93
|
+
yt_new, yp_new, average="micro", zero_division=0.0, beta=1.0
|
|
94
|
+
)
|
|
95
|
+
if not np.allclose([p_old, r_old, f_old], [p_new, r_new, f_new]):
|
|
96
|
+
mismatches += 1
|
|
97
|
+
print(f" MISMATCH (fscore) trial={trial} y_true={y_true} y_pred={y_pred}")
|
|
98
|
+
print(f" old: p={p_old} r={r_old} f={f_old}")
|
|
99
|
+
print(f" new: p={p_new} r={r_new} f={f_new}")
|
|
100
|
+
|
|
101
|
+
print(
|
|
102
|
+
f"[equivalence] checked {checked} non-trivial trials, {mismatches} mismatches"
|
|
103
|
+
)
|
|
104
|
+
return mismatches
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def check_single_column_edge_case() -> None:
|
|
108
|
+
"""Demonstrates why a densify fallback is needed when there is only one
|
|
109
|
+
unique label across a batch: sklearn's type_of_target() requires more
|
|
110
|
+
than one column to classify a target as "multilabel-indicator" (which
|
|
111
|
+
has native sparse support). With exactly one column it's classified as
|
|
112
|
+
"binary" instead, and sklearn's binary-handling path (column_or_1d)
|
|
113
|
+
rejects sparse input outright -- regardless of dense vs sparse, the
|
|
114
|
+
*classification* is the same; only the *acceptance* of sparse input
|
|
115
|
+
differs downstream.
|
|
116
|
+
"""
|
|
117
|
+
mlb = MultiLabelBinarizer(sparse_output=True)
|
|
118
|
+
y_true = [["a"], []]
|
|
119
|
+
y_pred = [["a"], ["a"]]
|
|
120
|
+
mlb.fit(y_true + y_pred)
|
|
121
|
+
at_sparse = mlb.transform(y_true)
|
|
122
|
+
af_sparse = mlb.transform(y_pred)
|
|
123
|
+
at_dense, af_dense = at_sparse.toarray(), af_sparse.toarray()
|
|
124
|
+
|
|
125
|
+
print(f"[single-column] shape={at_sparse.shape}")
|
|
126
|
+
print(f" type_of_target(sparse) = {type_of_target(at_sparse)!r}")
|
|
127
|
+
print(f" type_of_target(dense) = {type_of_target(at_dense)!r}")
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
precision_recall_fscore_support(
|
|
131
|
+
at_sparse, af_sparse, average="micro", zero_division=0.0, beta=1.0
|
|
132
|
+
)
|
|
133
|
+
print(" sparse input: unexpectedly succeeded")
|
|
134
|
+
except TypeError as e:
|
|
135
|
+
print(f" sparse input raises TypeError: {e}")
|
|
136
|
+
|
|
137
|
+
p, r, f, _ = precision_recall_fscore_support(
|
|
138
|
+
at_dense, af_dense, average="micro", zero_division=0.0, beta=1.0
|
|
139
|
+
)
|
|
140
|
+
print(f" dense input succeeds: precision={p} recall={r} fscore={f}")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def check_sample_weight_equivalence() -> None:
|
|
144
|
+
"""Confirms sample_weight produces identical results for sparse vs. dense
|
|
145
|
+
once the label space has more than one column (the common case)."""
|
|
146
|
+
mlb = MultiLabelBinarizer(sparse_output=True)
|
|
147
|
+
y_true = [["a", "b"], ["c"]]
|
|
148
|
+
y_pred = [["a"], ["c", "d"]]
|
|
149
|
+
mlb.fit(y_true + y_pred)
|
|
150
|
+
at_sparse = mlb.transform(y_true)
|
|
151
|
+
af_sparse = mlb.transform(y_pred)
|
|
152
|
+
weights = [2.0, 3.0]
|
|
153
|
+
|
|
154
|
+
p1, r1, f1, _ = precision_recall_fscore_support(
|
|
155
|
+
at_sparse,
|
|
156
|
+
af_sparse,
|
|
157
|
+
average="micro",
|
|
158
|
+
zero_division=0.0,
|
|
159
|
+
beta=1.0,
|
|
160
|
+
sample_weight=weights,
|
|
161
|
+
)
|
|
162
|
+
p2, r2, f2, _ = precision_recall_fscore_support(
|
|
163
|
+
at_sparse.toarray(),
|
|
164
|
+
af_sparse.toarray(),
|
|
165
|
+
average="micro",
|
|
166
|
+
zero_division=0.0,
|
|
167
|
+
beta=1.0,
|
|
168
|
+
sample_weight=weights,
|
|
169
|
+
)
|
|
170
|
+
print(f"[sample_weight] sparse: precision={p1} recall={r1} fscore={f1}")
|
|
171
|
+
print(f"[sample_weight] dense: precision={p2} recall={r2} fscore={f2}")
|
|
172
|
+
assert np.allclose([p1, r1, f1], [p2, r2, f2])
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def benchmark_dense_vs_sparse(n_samples: int = 2000, n_labels: int = 20000) -> None:
|
|
176
|
+
"""Demonstrates the actual cost of `.toarray()` that motivated this fix:
|
|
177
|
+
a label-indicator matrix scales as n_samples x n_unique_labels, which for
|
|
178
|
+
a large ID space (e.g. grant DOIs) becomes very large very fast, even
|
|
179
|
+
though the underlying data is highly sparse (each sample only has a
|
|
180
|
+
handful of true/predicted labels)."""
|
|
181
|
+
rng = random.Random(7)
|
|
182
|
+
vocab = [f"id{i}" for i in range(n_labels)]
|
|
183
|
+
y_true = [rng.sample(vocab, k=rng.randint(0, 3)) for _ in range(n_samples)]
|
|
184
|
+
y_pred = [rng.sample(vocab, k=rng.randint(0, 3)) for _ in range(n_samples)]
|
|
185
|
+
|
|
186
|
+
mlb = MultiLabelBinarizer(sparse_output=True)
|
|
187
|
+
mlb.fit(y_true + y_pred)
|
|
188
|
+
at_sparse = mlb.transform(y_true)
|
|
189
|
+
af_sparse = mlb.transform(y_pred)
|
|
190
|
+
print(
|
|
191
|
+
f"[benchmark] n_samples={n_samples} n_labels={n_labels} "
|
|
192
|
+
f"matrix shape={at_sparse.shape}"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
tracemalloc.start()
|
|
196
|
+
start = time.perf_counter()
|
|
197
|
+
old_dense_false_positives_negatives(at_sparse, af_sparse)
|
|
198
|
+
dense_time = time.perf_counter() - start
|
|
199
|
+
_, dense_peak = tracemalloc.get_traced_memory()
|
|
200
|
+
tracemalloc.stop()
|
|
201
|
+
|
|
202
|
+
tracemalloc.start()
|
|
203
|
+
start = time.perf_counter()
|
|
204
|
+
new_sparse_false_positives_negatives(at_sparse, af_sparse)
|
|
205
|
+
sparse_time = time.perf_counter() - start
|
|
206
|
+
_, sparse_peak = tracemalloc.get_traced_memory()
|
|
207
|
+
tracemalloc.stop()
|
|
208
|
+
|
|
209
|
+
print(
|
|
210
|
+
f"[benchmark] dense (.toarray) : {dense_time:.3f}s, "
|
|
211
|
+
f"peak extra memory {dense_peak / 1e6:.1f} MB"
|
|
212
|
+
)
|
|
213
|
+
print(
|
|
214
|
+
f"[benchmark] sparse (.multiply): {sparse_time:.3f}s, "
|
|
215
|
+
f"peak extra memory {sparse_peak / 1e6:.1f} MB"
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def main() -> None:
|
|
220
|
+
print(
|
|
221
|
+
"== 1. Old vs. new false_positives/false_negatives/precision/recall/f-score equivalence =="
|
|
222
|
+
)
|
|
223
|
+
mismatches = check_equivalence()
|
|
224
|
+
|
|
225
|
+
print("\n== 2. Single-label-column edge case (why the densify fallback exists) ==")
|
|
226
|
+
check_single_column_edge_case()
|
|
227
|
+
|
|
228
|
+
print("\n== 3. sample_weight equivalence (sparse vs. dense) ==")
|
|
229
|
+
check_sample_weight_equivalence()
|
|
230
|
+
|
|
231
|
+
print("\n== 4. Benchmark: dense .toarray() vs. sparse, large label space ==")
|
|
232
|
+
benchmark_dense_vs_sparse()
|
|
233
|
+
|
|
234
|
+
print("\n== Summary ==")
|
|
235
|
+
print(f"Equivalence mismatches: {mismatches} (expect 0)")
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
if __name__ == "__main__":
|
|
239
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.3.1
|
|
@@ -20,17 +20,25 @@ import logging
|
|
|
20
20
|
|
|
21
21
|
logger = logging.getLogger(__name__)
|
|
22
22
|
|
|
23
|
+
STRATEGIES_DIR = "crossref_matcher/strategies"
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
|
|
26
|
+
def pascal_to_snake_case(s: str) -> str:
|
|
25
27
|
return re.sub(r"(?<!^)(?=[A-Z])", "_", s).lower()
|
|
26
28
|
|
|
27
29
|
|
|
28
|
-
def kebab_to_snake_case(string: str) -> str:
|
|
30
|
+
def kebab_to_snake_case(string: str | None) -> str | None:
|
|
29
31
|
if string is None:
|
|
30
32
|
return None
|
|
31
33
|
return "_".join(string.split("-"))
|
|
32
34
|
|
|
33
35
|
|
|
36
|
+
def snake_to_kebab_case(string: str | None) -> str | None:
|
|
37
|
+
if string is None:
|
|
38
|
+
return None
|
|
39
|
+
return "-".join(string.split("_"))
|
|
40
|
+
|
|
41
|
+
|
|
34
42
|
class NoDefaultStrategyError(ValueError):
|
|
35
43
|
"""Raised when no default strategy is configured for a task."""
|
|
36
44
|
|
|
@@ -57,6 +65,7 @@ class Strategy(ABC):
|
|
|
57
65
|
id: str
|
|
58
66
|
task: MatchTask
|
|
59
67
|
description: str
|
|
68
|
+
version: str | None = None
|
|
60
69
|
|
|
61
70
|
@classmethod
|
|
62
71
|
def create_dummy_subclass(cls, module_name: str, error: str):
|
|
@@ -230,6 +239,13 @@ class Strategy(ABC):
|
|
|
230
239
|
raise TypeError(
|
|
231
240
|
f"Class {cls.__name__} must have a class field '{name}' of type {type.__name__}."
|
|
232
241
|
)
|
|
242
|
+
try:
|
|
243
|
+
pkg = cls.__module__.removesuffix(".strategy")
|
|
244
|
+
version_ref = resources.files(pkg) / "version.txt"
|
|
245
|
+
if version_ref.is_file():
|
|
246
|
+
cls.version = version_ref.read_text().strip()
|
|
247
|
+
except (TypeError, OSError, ModuleNotFoundError):
|
|
248
|
+
pass
|
|
233
249
|
|
|
234
250
|
@classmethod
|
|
235
251
|
def is_default(cls):
|
|
@@ -274,7 +290,7 @@ def list_strategies(
|
|
|
274
290
|
|
|
275
291
|
|
|
276
292
|
def get_strategy_by_id(
|
|
277
|
-
strategy_id: str | None,
|
|
293
|
+
strategy_id: str | None = None,
|
|
278
294
|
task: MatchTask | str | None = None,
|
|
279
295
|
allow_disabled: bool = False,
|
|
280
296
|
) -> type[Strategy]:
|
|
@@ -298,7 +314,11 @@ def get_strategy_by_id(
|
|
|
298
314
|
raise ValueError("Must specify at least one of [strategy_id, task]")
|
|
299
315
|
return get_default_strategy(task)
|
|
300
316
|
strategy_found = False
|
|
301
|
-
for id_to_check in [
|
|
317
|
+
for id_to_check in [
|
|
318
|
+
strategy_id,
|
|
319
|
+
kebab_to_snake_case(strategy_id),
|
|
320
|
+
snake_to_kebab_case(strategy_id),
|
|
321
|
+
]:
|
|
302
322
|
strategy = [
|
|
303
323
|
c
|
|
304
324
|
for c in list_strategies(task=task, include_disabled=allow_disabled)
|
|
@@ -354,7 +374,7 @@ def get_strategy_by_module_name(
|
|
|
354
374
|
|
|
355
375
|
|
|
356
376
|
def get_strategy(
|
|
357
|
-
strategy_id: str | None,
|
|
377
|
+
strategy_id: str | None = None,
|
|
358
378
|
task: MatchTask | str | None = None,
|
|
359
379
|
allow_disabled: bool = False,
|
|
360
380
|
) -> type[Strategy]:
|
crossref_matcher-1.3.1/crossref_matcher/strategies/funder/funder_name_to_ror_search/version.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.0
|
|
File without changes
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import subprocess
|
|
3
|
+
from pathlib import PurePosixPath
|
|
4
|
+
|
|
5
|
+
from crossref_matcher.strategies import STRATEGIES_DIR
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
_STRATEGIES_PATH = PurePosixPath(STRATEGIES_DIR)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _strategy_dir(path: str) -> str | None:
|
|
13
|
+
"""Return 'task/strategy' for a path inside the strategies dir, or None."""
|
|
14
|
+
try:
|
|
15
|
+
rel = PurePosixPath(path).relative_to(_STRATEGIES_PATH)
|
|
16
|
+
except ValueError:
|
|
17
|
+
return None
|
|
18
|
+
if len(rel.parts) < 3: # need task / strategy / filename at minimum
|
|
19
|
+
return None
|
|
20
|
+
return f"{rel.parts[0]}/{rel.parts[1]}"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_changed_files(base: str | None, staged: bool) -> list[str]:
|
|
24
|
+
if staged:
|
|
25
|
+
result = subprocess.run(
|
|
26
|
+
["git", "diff", "--cached", "--name-only"],
|
|
27
|
+
capture_output=True,
|
|
28
|
+
text=True,
|
|
29
|
+
check=True,
|
|
30
|
+
)
|
|
31
|
+
elif base:
|
|
32
|
+
result = subprocess.run(
|
|
33
|
+
["git", "diff", "--name-only", f"{base}...HEAD"],
|
|
34
|
+
capture_output=True,
|
|
35
|
+
text=True,
|
|
36
|
+
check=True,
|
|
37
|
+
)
|
|
38
|
+
else:
|
|
39
|
+
raise ValueError("Must specify --base or --staged")
|
|
40
|
+
return result.stdout.splitlines()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_changed_strategy_dirs(changed_files: list[str]) -> set[str]:
|
|
44
|
+
return {
|
|
45
|
+
_strategy_dir(f)
|
|
46
|
+
for f in changed_files
|
|
47
|
+
if PurePosixPath(f).name != "version.txt"
|
|
48
|
+
if _strategy_dir(f) is not None
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def check(base: str | None, staged: bool) -> bool:
|
|
53
|
+
changed_files = get_changed_files(base, staged)
|
|
54
|
+
changed_dirs = get_changed_strategy_dirs(changed_files)
|
|
55
|
+
|
|
56
|
+
if not changed_dirs:
|
|
57
|
+
return True
|
|
58
|
+
|
|
59
|
+
bumped_dirs = {
|
|
60
|
+
_strategy_dir(f)
|
|
61
|
+
for f in changed_files
|
|
62
|
+
if PurePosixPath(f).name == "version.txt"
|
|
63
|
+
if _strategy_dir(f) is not None
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
failures = sorted(changed_dirs - bumped_dirs)
|
|
67
|
+
if failures:
|
|
68
|
+
missing = "\n".join(f" {STRATEGIES_DIR}/{d}/version.txt" for d in failures)
|
|
69
|
+
logger.error(
|
|
70
|
+
"The following strategies have changes but no version bump:\n%s\n"
|
|
71
|
+
"\nRun: python scripts/bump_strategy_version.py <strategy-path> <major|minor>"
|
|
72
|
+
"\nTo skip: rerun with --force, or set SKIP_BUMP_CHECK=1",
|
|
73
|
+
missing,
|
|
74
|
+
)
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
return True
|
|
@@ -20,6 +20,8 @@ crossref_matcher/evaluation/evaluation.py
|
|
|
20
20
|
crossref_matcher/evaluation/google_sheets_connector.py
|
|
21
21
|
crossref_matcher/evaluation/ror_utils.py
|
|
22
22
|
crossref_matcher/evaluation/schemas.py
|
|
23
|
+
crossref_matcher/evaluation/sparse_metrics_verification/__init__.py
|
|
24
|
+
crossref_matcher/evaluation/sparse_metrics_verification/verify_sparse_metrics.py
|
|
23
25
|
crossref_matcher/indexes/__init__.py
|
|
24
26
|
crossref_matcher/indexes/preprint_matching/__init__.py
|
|
25
27
|
crossref_matcher/indexes/preprint_matching/indexer_service.py
|
|
@@ -69,6 +71,7 @@ crossref_matcher/resources/data/models/reference_nn_unstructured/eval/events.out
|
|
|
69
71
|
crossref_matcher/resources/data/models/reference_nn_unstructured/train/events.out.tfevents.1681511550.knopfler.local
|
|
70
72
|
crossref_matcher/strategies/__init__.py
|
|
71
73
|
crossref_matcher/strategies/common.py
|
|
74
|
+
crossref_matcher/strategies/versioning.py
|
|
72
75
|
crossref_matcher/strategies/affiliation/__init__.py
|
|
73
76
|
crossref_matcher/strategies/affiliation/es_utils.py
|
|
74
77
|
crossref_matcher/strategies/affiliation/matching.py
|
|
@@ -80,6 +83,7 @@ crossref_matcher/strategies/affiliation/single_search/strategy.py
|
|
|
80
83
|
crossref_matcher/strategies/funder/__init__.py
|
|
81
84
|
crossref_matcher/strategies/funder/funder_name_to_ror_search/__init__.py
|
|
82
85
|
crossref_matcher/strategies/funder/funder_name_to_ror_search/strategy.py
|
|
86
|
+
crossref_matcher/strategies/funder/funder_name_to_ror_search/version.txt
|
|
83
87
|
crossref_matcher/strategies/healthcheck/__init__.py
|
|
84
88
|
crossref_matcher/strategies/healthcheck/healthcheck/__init__.py
|
|
85
89
|
crossref_matcher/strategies/healthcheck/healthcheck/strategy.py
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1.2.2
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher/evaluation/dataset_ingest.py
RENAMED
|
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
|
{crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{crossref_matcher-1.2.2 → crossref_matcher-1.3.1}/crossref_matcher.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|