crossref-matcher 1.3.1__tar.gz → 1.3.2__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 (132) hide show
  1. {crossref_matcher-1.3.1/crossref_matcher.egg-info → crossref_matcher-1.3.2}/PKG-INFO +1 -1
  2. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/README.md +93 -0
  3. crossref_matcher-1.3.2/crossref_matcher/indexes/ror_organizations/opensearch_utils.py +44 -0
  4. crossref_matcher-1.3.2/crossref_matcher/resources/VERSION.txt +1 -0
  5. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2/crossref_matcher.egg-info}/PKG-INFO +1 -1
  6. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher.egg-info/SOURCES.txt +1 -1
  7. crossref_matcher-1.3.1/crossref_matcher/indexes/ror_organizations/ror_opensearch_update_dag.py +0 -124
  8. crossref_matcher-1.3.1/crossref_matcher/resources/VERSION.txt +0 -1
  9. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/LICENSE +0 -0
  10. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/MANIFEST.in +0 -0
  11. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/MatchTask.py +0 -0
  12. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/__init__.py +0 -0
  13. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/app.py +0 -0
  14. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/evaluation/__init__.py +0 -0
  15. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/evaluation/all_links.py +0 -0
  16. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/evaluation/api_routes.py +0 -0
  17. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/evaluation/dataset_ingest.py +0 -0
  18. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/evaluation/evaluation.py +0 -0
  19. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/evaluation/google_sheets_connector.py +0 -0
  20. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/evaluation/ror_utils.py +0 -0
  21. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/evaluation/schemas.py +0 -0
  22. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/evaluation/sparse_metrics_verification/__init__.py +0 -0
  23. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/evaluation/sparse_metrics_verification/verify_sparse_metrics.py +0 -0
  24. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/indexes/__init__.py +0 -0
  25. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/indexes/preprint_matching/__init__.py +0 -0
  26. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/indexes/preprint_matching/indexer_service.py +0 -0
  27. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/indexes/ror_organizations/__init__.py +0 -0
  28. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/indexes/ror_organizations/create_index.py +0 -0
  29. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/indexes/ror_organizations/index_data.py +0 -0
  30. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/indexes/ror_organizations/ror_zenodo_api.py +0 -0
  31. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/matching/__init__.py +0 -0
  32. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/matching/http_utils.py +0 -0
  33. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/matching/run_strategy_on_dataset.py +0 -0
  34. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/matching/schemas.py +0 -0
  35. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/matching/search.py +0 -0
  36. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/matching/utils.py +0 -0
  37. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/__init__.py +0 -0
  38. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/affiliation/__init__.py +0 -0
  39. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/__init__.py +0 -0
  40. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/common_ror_endings/__init__.py +0 -0
  41. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/common_ror_endings/common_ror_endings.txt +0 -0
  42. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/countries/__init__.py +0 -0
  43. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/countries/china_placenames.txt +0 -0
  44. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/countries/countries.txt +0 -0
  45. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/countries/countries2.txt +0 -0
  46. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/datasets/__init__.py +0 -0
  47. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/models/__init__.py +0 -0
  48. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/models/preprint_ensemble_ml/__init__.py +0 -0
  49. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/models/preprint_ensemble_ml/data_counters0.pkl +0 -0
  50. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/models/preprint_ensemble_ml/eval/events.out.tfevents.1690572611.knopfler.local +0 -0
  51. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/models/preprint_ensemble_ml/model.opt_slots0.npy.gz +0 -0
  52. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/models/preprint_ensemble_ml/model.pkl.gz +0 -0
  53. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/models/preprint_ensemble_ml/model.weights.npy.gz +0 -0
  54. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/models/preprint_ensemble_ml/train/events.out.tfevents.1690572611.knopfler.local +0 -0
  55. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/models/preprint_ml/data_counters0.pkl +0 -0
  56. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/models/preprint_ml/eval/events.out.tfevents.1690302892.knopfler.local +0 -0
  57. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/models/preprint_ml/model.opt_slots0.npy.gz +0 -0
  58. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/models/preprint_ml/model.pkl.gz +0 -0
  59. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/models/preprint_ml/model.weights.npy.gz +0 -0
  60. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/models/preprint_ml/train/events.out.tfevents.1690302891.knopfler.local +0 -0
  61. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/models/reference_nn_unstructured/__init__.py +0 -0
  62. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/models/reference_nn_unstructured/data_counters0.pkl +0 -0
  63. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/models/reference_nn_unstructured/eval/events.out.tfevents.1681511550.knopfler.local +0 -0
  64. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/models/reference_nn_unstructured/model.opt_slots0.npy.gz +0 -0
  65. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/models/reference_nn_unstructured/model.pkl.gz +0 -0
  66. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/models/reference_nn_unstructured/model.weights.npy.gz +0 -0
  67. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/data/models/reference_nn_unstructured/train/events.out.tfevents.1681511550.knopfler.local +0 -0
  68. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/resources/plugins.json +0 -0
  69. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/run_plugin_test.py +0 -0
  70. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/__init__.py +0 -0
  71. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/affiliation/__init__.py +0 -0
  72. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/affiliation/es_utils.py +0 -0
  73. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/affiliation/matching.py +0 -0
  74. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/affiliation/multi_search/__init__.py +0 -0
  75. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/affiliation/multi_search/strategy.py +0 -0
  76. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/affiliation/single_search/__init__.py +0 -0
  77. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/affiliation/single_search/strategy.py +0 -0
  78. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/affiliation/strategy.py +0 -0
  79. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/common.py +0 -0
  80. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/funder/__init__.py +0 -0
  81. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/funder/funder_name_to_ror_search/__init__.py +0 -0
  82. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/funder/funder_name_to_ror_search/strategy.py +0 -0
  83. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/funder/funder_name_to_ror_search/version.txt +0 -0
  84. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/healthcheck/__init__.py +0 -0
  85. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/healthcheck/healthcheck/__init__.py +0 -0
  86. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/healthcheck/healthcheck/strategy.py +0 -0
  87. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/preprint/__init__.py +0 -0
  88. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/preprint/ensemble_avg/__init__.py +0 -0
  89. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/preprint/ensemble_avg/strategy.py +0 -0
  90. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/preprint/ensemble_cascade/__init__.py +0 -0
  91. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/preprint/ensemble_cascade/strategy.py +0 -0
  92. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/preprint/ensemble_ml/__init__.py +0 -0
  93. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/preprint/ensemble_ml/get_train_data.py +0 -0
  94. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/preprint/ensemble_ml/strategy.py +0 -0
  95. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/preprint/ensemble_ml/train.py +0 -0
  96. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/preprint/ensemble_ml/utils.py +0 -0
  97. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/preprint/preprint_ml/__init__.py +0 -0
  98. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/preprint/preprint_ml/get_train_data.py +0 -0
  99. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/preprint/preprint_ml/strategy.py +0 -0
  100. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/preprint/preprint_ml/train.py +0 -0
  101. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/preprint/preprint_ml/utils.py +0 -0
  102. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/preprint/sbmv/__init__.py +0 -0
  103. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/preprint/sbmv/strategy.py +0 -0
  104. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/preprint/sbmv_es/__init__.py +0 -0
  105. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/preprint/sbmv_es/strategy.py +0 -0
  106. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/preprint/sbmv_rels/__init__.py +0 -0
  107. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/preprint/sbmv_rels/strategy.py +0 -0
  108. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/reference/__init__.py +0 -0
  109. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/reference/cs_combined/__init__.py +0 -0
  110. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/reference/cs_combined/strategy.py +0 -0
  111. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/reference/nn_unstructured/__init__.py +0 -0
  112. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/reference/nn_unstructured/get_train_data.py +0 -0
  113. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/reference/nn_unstructured/strategy.py +0 -0
  114. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/reference/nn_unstructured/train.py +0 -0
  115. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/reference/nn_unstructured/utils.py +0 -0
  116. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/reference/parsed_doi/__init__.py +0 -0
  117. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/reference/parsed_doi/strategy.py +0 -0
  118. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/reference/parsed_isbn/__init__.py +0 -0
  119. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/reference/parsed_isbn/strategy.py +0 -0
  120. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/reference/sbmv/__init__.py +0 -0
  121. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/reference/sbmv/strategy.py +0 -0
  122. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/reference/sbmv_orig/__init__.py +0 -0
  123. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/reference/sbmv_orig/strategy.py +0 -0
  124. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/reference/sbmv_unstructured/__init__.py +0 -0
  125. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/reference/sbmv_unstructured/strategy.py +0 -0
  126. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher/strategies/versioning.py +0 -0
  127. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher.egg-info/dependency_links.txt +0 -0
  128. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher.egg-info/entry_points.txt +0 -0
  129. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher.egg-info/requires.txt +0 -0
  130. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/crossref_matcher.egg-info/top_level.txt +0 -0
  131. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/setup.cfg +0 -0
  132. {crossref_matcher-1.3.1 → crossref_matcher-1.3.2}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: crossref-matcher
3
- Version: 1.3.1
3
+ Version: 1.3.2
4
4
  Summary: Crossref's matching API
5
5
  Home-page: https://gitlab.com/crossref/marple
6
6
  Author: Crossref
@@ -185,3 +185,96 @@ Requirements are defined in `setup.py`. Requirements files with exact versions c
185
185
  * To update `requirements.txt` file: `pip-compile setup.py --extra strategies-core`
186
186
 
187
187
  * To update `requirements-tests.txt` file: `pip-compile setup.py --extra strategies-core --extra evaluation --extra test -o requirements-tests.txt`
188
+
189
+ ## Branching and deployment
190
+
191
+ CI (see [.gitlab-ci.yml](.gitlab-ci.yml)) deploys from two long-lived branches:
192
+
193
+ | Branch / trigger | Deploys to | Jobs |
194
+ |---|---|---|
195
+ | `dev` | **Research** environment | ECS deploy (`marple-research`), Airflow DAG data sync to `s3://airflow.crossref.org` (research-only bucket), `X.Y.Z-dev.N` prerelease tags |
196
+ | `main` | **Staging** environment | ECS deploy (`marple-staging`), GitLab-registry image for the integration project, release tags via semantic-release |
197
+ | Stable `vX.Y.Z` tags | PyPI | `crossref-matcher` published via OIDC trusted publishing (prerelease tags are not published) |
198
+
199
+ Both branches are protected. `main` is merged to via MR only; `dev`'s
200
+ protection must also allow Developers to push and merge directly
201
+ (**Settings → Repository → Protected branches → `dev` → Allowed to push
202
+ and merge**), since the workflow below pushes to it directly. Deploys are
203
+ automatic on push and are keyed on the commit SHA (`$CI_COMMIT_SHORT_SHA`),
204
+ independent of the semantic version.
205
+
206
+ ### Feature workflow
207
+
208
+ 1. **Branch off `main`** (not `dev`), so the feature can merge to `main`
209
+ cleanly regardless of what else is being tested on `dev`.
210
+ 2. **To test on the research environment**, merge the feature into `dev` and
211
+ push (no MR needed — review happens at the `main` MR):
212
+
213
+ ```sh
214
+ git checkout dev && git pull
215
+ git merge --no-ff my-feature
216
+ git push
217
+ ```
218
+
219
+ 3. **When validated, open an MR from the feature branch into `main`.**
220
+ Do not merge `dev` into `main` — `dev` may contain other unfinished work.
221
+ Use a [conventional commit](https://www.conventionalcommits.org/) message
222
+ (`feat:`/`fix:`) for the squash/merge commit; semantic-release derives the
223
+ release version from it.
224
+ 4. **After the MR merges, merge `main` back into `dev`** to keep the branches
225
+ converged. This isn't strictly required — semantic-release's prerelease
226
+ branches skip the "behind the release branch" check — but without it,
227
+ `dev`'s next prerelease can recompute a version `main` already released,
228
+ or a confusing `vX.Y.Z-dev.N` alongside an already-shipped `vX.Y.Z`:
229
+
230
+ ```sh
231
+ git checkout dev && git pull
232
+ git merge origin/main
233
+ git push
234
+ ```
235
+
236
+ ### Versioning
237
+
238
+ Releases are cut by semantic-release from conventional commit messages
239
+ (`fix:` → patch, `feat:` → minor, `BREAKING CHANGE` → major; other types cut
240
+ no release). On `dev`, release-worthy commits produce prerelease tags like
241
+ `v1.3.0-dev.1` (configured in [.releaserc.json](.releaserc.json)). Commits
242
+ without a `feat:`/`fix:` still deploy — only the tag is skipped, and
243
+ `VERSION.txt` keeps its `0.0.dev0` placeholder. To identify what is running
244
+ in an environment, use the commit SHA (the ECS image tag), not the version
245
+ string.
246
+
247
+ ### Resetting a polluted `dev` back to `main`
248
+
249
+ `dev` is disposable: `main` never depends on its history. If `dev` has
250
+ accumulated abandoned experiments, reset it to `main` and re-merge only the
251
+ features still in flight:
252
+
253
+ 1. Temporarily enable force-push for `dev` in GitLab
254
+ (**Settings → Repository → Protected branches → `dev` → Allowed to force push**).
255
+ 2. Delete any `vX.Y.Z-dev.N` prerelease tags cut on the old `dev` — after the
256
+ reset their commits are unreachable from the branch, so semantic-release
257
+ won't see them and will try to recreate the same tag, failing every
258
+ subsequent release-worthy `dev` push until they're gone:
259
+
260
+ ```sh
261
+ git tag -l 'v*-dev.*' | xargs -r git push origin --delete
262
+ git tag -l 'v*-dev.*' | xargs -r git tag -d
263
+ ```
264
+
265
+ 3. Reset and push:
266
+
267
+ ```sh
268
+ git fetch origin
269
+ git checkout dev
270
+ git reset --hard origin/main
271
+ git push --force-with-lease origin dev
272
+ git merge --no-ff still-in-flight-feature # re-merge anything still being tested
273
+ git push
274
+ ```
275
+
276
+ 4. Disable force-push again.
277
+
278
+ The reset push re-deploys research and re-syncs the Airflow DAG data in
279
+ `s3://airflow.crossref.org` to the reset state (the sync uses `--delete`, so
280
+ files added by abandoned experiments are removed, not just left stale).
@@ -0,0 +1,44 @@
1
+ """Shared OpenSearch client utilities for ROR index DAG tasks."""
2
+
3
+ import base64
4
+ import boto3
5
+
6
+ REGION = "us-east-1"
7
+ INDEX = "organization"
8
+
9
+
10
+ def get_secret(secret_name: str) -> str | bytes:
11
+ """Retrieve a secret value from AWS Secrets Manager."""
12
+ session = boto3.session.Session()
13
+ client = session.client(service_name="secretsmanager", region_name=REGION)
14
+
15
+ get_secret_value_response = client.get_secret_value(SecretId=secret_name)
16
+ if "SecretString" in get_secret_value_response:
17
+ return get_secret_value_response["SecretString"]
18
+ else:
19
+ return base64.b64decode(get_secret_value_response["SecretBinary"])
20
+
21
+
22
+ def get_es_client(host: str | None = None):
23
+ """Create an authenticated OpenSearch client using AWS credentials."""
24
+ from opensearchpy import (
25
+ OpenSearch,
26
+ RequestsHttpConnection,
27
+ AWSV4SignerAuth,
28
+ )
29
+
30
+ if host is None:
31
+ host = get_secret("es-host-matching")
32
+
33
+ credentials = boto3.Session().get_credentials()
34
+ auth = AWSV4SignerAuth(credentials, REGION, "es")
35
+
36
+ return OpenSearch(
37
+ hosts=[{"host": host, "port": 443}],
38
+ http_auth=auth,
39
+ use_ssl=True,
40
+ verify_certs=True,
41
+ connection_class=RequestsHttpConnection,
42
+ pool_maxsize=20,
43
+ timeout=120,
44
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: crossref-matcher
3
- Version: 1.3.1
3
+ Version: 1.3.2
4
4
  Summary: Crossref's matching API
5
5
  Home-page: https://gitlab.com/crossref/marple
6
6
  Author: Crossref
@@ -28,7 +28,7 @@ crossref_matcher/indexes/preprint_matching/indexer_service.py
28
28
  crossref_matcher/indexes/ror_organizations/__init__.py
29
29
  crossref_matcher/indexes/ror_organizations/create_index.py
30
30
  crossref_matcher/indexes/ror_organizations/index_data.py
31
- crossref_matcher/indexes/ror_organizations/ror_opensearch_update_dag.py
31
+ crossref_matcher/indexes/ror_organizations/opensearch_utils.py
32
32
  crossref_matcher/indexes/ror_organizations/ror_zenodo_api.py
33
33
  crossref_matcher/matching/__init__.py
34
34
  crossref_matcher/matching/http_utils.py
@@ -1,124 +0,0 @@
1
- """Airflow DAG to keep the ROR OpenSearch index up to date."""
2
-
3
- import base64
4
- import boto3
5
-
6
- from airflow.decorators import dag, task
7
- from datetime import datetime
8
- from datetime import timedelta
9
- from opensearchpy import (
10
- OpenSearch,
11
- RequestsHttpConnection,
12
- AWSV4SignerAuth,
13
- NotFoundError,
14
- )
15
-
16
- import logging
17
- from typing import Any
18
-
19
- from crossref_matcher.indexes.ror_organizations.ror_zenodo_api import (
20
- get_ror_data_dump_metadata,
21
- )
22
- from crossref_matcher.indexes.ror_organizations.create_index import (
23
- index_body,
24
- initialize_index,
25
- )
26
- from crossref_matcher.indexes.ror_organizations.index_data import (
27
- download_ror_data_and_update_index,
28
- )
29
-
30
- logger = logging.getLogger(__name__)
31
-
32
- REGION = "us-east-1"
33
- INDEX = "organization"
34
- INDEX_BATCH_SIZE = 400
35
-
36
-
37
- def get_secret(secret_name: str) -> str | bytes:
38
- """Retrieve a secret value from AWS Secrets Manager."""
39
- session = boto3.session.Session()
40
- client = session.client(service_name="secretsmanager", region_name=REGION)
41
-
42
- get_secret_value_response = client.get_secret_value(SecretId=secret_name)
43
- if "SecretString" in get_secret_value_response:
44
- return get_secret_value_response["SecretString"]
45
- else:
46
- return base64.b64decode(get_secret_value_response["SecretBinary"])
47
-
48
-
49
- def get_es_client() -> OpenSearch:
50
- """Create an authenticated OpenSearch client using AWS credentials."""
51
- credentials = boto3.Session().get_credentials()
52
- auth = AWSV4SignerAuth(credentials, REGION, "es")
53
-
54
- return OpenSearch(
55
- hosts=[{"host": get_secret("es-host-matching"), "port": 443}],
56
- http_auth=auth,
57
- use_ssl=True,
58
- verify_certs=True,
59
- connection_class=RequestsHttpConnection,
60
- pool_maxsize=20,
61
- )
62
-
63
-
64
- @dag(
65
- schedule_interval="@daily",
66
- catchup=False,
67
- dagrun_timeout=timedelta(hours=10),
68
- start_date=datetime(2026, 1, 3),
69
- tags=["experiment", "ror", "matching"],
70
- )
71
- def update_ror_index():
72
- @task()
73
- def fetch_ror_metadata() -> dict[str, Any]:
74
- """Fetch metadata for the latest ROR data dump from Zenodo."""
75
- return get_ror_data_dump_metadata()
76
-
77
- @task.short_circuit()
78
- def is_update_needed(metadata: dict[str, Any]) -> bool:
79
- """Return True if the index checksum differs from the latest dump."""
80
- ror_checksum = metadata["checksum"]
81
- es_client = get_es_client()
82
-
83
- # # FOR TESTING: RUN THIS TO FORCE UPDATE, OTHERWISE COMMENT OUT
84
- # index_metadata = {
85
- # "target_data": "",
86
- # "ror_data_dump_checksum": "",
87
- # "ror_data_dump_doi": "",
88
- # }
89
- # body = {"_meta": index_metadata}
90
- # es_client.indices.put_mapping(index=INDEX, body=body)
91
-
92
- try:
93
- mapping = es_client.indices.get_mapping(index=INDEX)
94
- except NotFoundError:
95
- return True
96
- meta = mapping[INDEX]["mappings"].get("_meta", {})
97
- current_checksum = meta.get("ror_data_dump_checksum", "")
98
- return current_checksum != ror_checksum
99
-
100
- @task()
101
- def create_index_if_not_exists():
102
- es_client = get_es_client()
103
-
104
- index_exists = es_client.indices.exists(index=INDEX)
105
-
106
- if not index_exists:
107
- index_body_content = index_body()
108
- response = initialize_index(INDEX, es_client, index_body_content)
109
- logger.info(f"Created index '{INDEX}': {response}")
110
-
111
- @task
112
- def download_and_update(metadata: dict[str, Any]) -> None:
113
- """Download the ROR data dump and update the index."""
114
- es_client = get_es_client()
115
- download_ror_data_and_update_index(metadata, es_client)
116
-
117
- metadata = fetch_ror_metadata()
118
- update_needed = is_update_needed(metadata)
119
- # if update_needed is false, the DAG will stop here
120
-
121
- update_needed >> create_index_if_not_exists() >> download_and_update(metadata)
122
-
123
-
124
- workflow = update_ror_index()