ckanext-citations 0.1.8__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.
- ckanext_citations-0.1.8/PKG-INFO +111 -0
- ckanext_citations-0.1.8/README.md +96 -0
- ckanext_citations-0.1.8/ckanext/__init__.py +8 -0
- ckanext_citations-0.1.8/ckanext/citations/__init__.py +0 -0
- ckanext_citations-0.1.8/ckanext/citations/cli.py +89 -0
- ckanext_citations-0.1.8/ckanext/citations/i18n/ckanext-citations.pot +116 -0
- ckanext_citations-0.1.8/ckanext/citations/i18n/fr/LC_MESSAGES/ckanext-citations.mo +0 -0
- ckanext_citations-0.1.8/ckanext/citations/i18n/fr/LC_MESSAGES/ckanext-citations.po +130 -0
- ckanext_citations-0.1.8/ckanext/citations/lib/__init__.py +0 -0
- ckanext_citations-0.1.8/ckanext/citations/lib/datacite_events_client.py +88 -0
- ckanext_citations-0.1.8/ckanext/citations/lib/helpers.py +74 -0
- ckanext_citations-0.1.8/ckanext/citations/lib/openalex_client.py +154 -0
- ckanext_citations-0.1.8/ckanext/citations/lib/package_extras.py +91 -0
- ckanext_citations-0.1.8/ckanext/citations/lib/refresh.py +255 -0
- ckanext_citations-0.1.8/ckanext/citations/lib/scoring.py +37 -0
- ckanext_citations-0.1.8/ckanext/citations/migration/citations/alembic.ini +41 -0
- ckanext_citations-0.1.8/ckanext/citations/migration/citations/env.py +50 -0
- ckanext_citations-0.1.8/ckanext/citations/migration/citations/versions/8f3b2c1a9d4e_initialisation.py +130 -0
- ckanext_citations-0.1.8/ckanext/citations/model/__init__.py +21 -0
- ckanext_citations-0.1.8/ckanext/citations/model/author_sindex.py +30 -0
- ckanext_citations-0.1.8/ckanext/citations/model/citation_stats.py +52 -0
- ckanext_citations-0.1.8/ckanext/citations/model/citing_researcher.py +51 -0
- ckanext_citations-0.1.8/ckanext/citations/model/citing_work.py +55 -0
- ckanext_citations-0.1.8/ckanext/citations/model/score_history.py +31 -0
- ckanext_citations-0.1.8/ckanext/citations/plugin.py +41 -0
- ckanext_citations-0.1.8/ckanext/citations/templates/package/read.html +8 -0
- ckanext_citations-0.1.8/ckanext/citations/templates/package/snippets/citations_panel.html +158 -0
- ckanext_citations-0.1.8/ckanext/citations/tests/__init__.py +0 -0
- ckanext_citations-0.1.8/ckanext/citations/tests/conftest.py +43 -0
- ckanext_citations-0.1.8/ckanext/citations/tests/test.ini +44 -0
- ckanext_citations-0.1.8/ckanext/citations/tests/test_db.py +130 -0
- ckanext_citations-0.1.8/ckanext/citations/tests/test_package_extras.py +74 -0
- ckanext_citations-0.1.8/ckanext/citations/tests/test_scoring.py +66 -0
- ckanext_citations-0.1.8/ckanext_citations.egg-info/PKG-INFO +111 -0
- ckanext_citations-0.1.8/ckanext_citations.egg-info/SOURCES.txt +42 -0
- ckanext_citations-0.1.8/ckanext_citations.egg-info/dependency_links.txt +1 -0
- ckanext_citations-0.1.8/ckanext_citations.egg-info/entry_points.txt +2 -0
- ckanext_citations-0.1.8/ckanext_citations.egg-info/not-zip-safe +1 -0
- ckanext_citations-0.1.8/ckanext_citations.egg-info/requires.txt +6 -0
- ckanext_citations-0.1.8/ckanext_citations.egg-info/top_level.txt +2 -0
- ckanext_citations-0.1.8/pyproject.toml +49 -0
- ckanext_citations-0.1.8/setup.cfg +26 -0
- ckanext_citations-0.1.8/setup.py +12 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: ckanext-citations
|
|
3
|
+
Version: 0.1.8
|
|
4
|
+
Summary: Citation tracking (cited-by via OpenAlex/DataCite Event Data), dataset disruption index and per-researcher S-index, displayed on the dataset page.
|
|
5
|
+
Author-email: ICS <groupe-info-ics@igbmc.fr>
|
|
6
|
+
License: AGPL-3.0-or-later
|
|
7
|
+
Keywords: CKAN,data,citations,bibliometrics
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: requests
|
|
11
|
+
Provides-Extra: test
|
|
12
|
+
Requires-Dist: pytest==7.4.4; extra == "test"
|
|
13
|
+
Requires-Dist: pytest-cov==4.1.0; extra == "test"
|
|
14
|
+
Requires-Dist: responses; extra == "test"
|
|
15
|
+
|
|
16
|
+

|
|
17
|
+

|
|
18
|
+
|
|
19
|
+
# ckanext-citations
|
|
20
|
+
|
|
21
|
+
Citation tracking (cited-by, via OpenAlex with a DataCite Event Data
|
|
22
|
+
fallback), a per-dataset disruption index, and a per-researcher S-index
|
|
23
|
+
("how many unique researchers has your data enabled" - see
|
|
24
|
+
[s-index.science](https://s-index.science/)), displayed on the dataset
|
|
25
|
+
page.
|
|
26
|
+
|
|
27
|
+
## v1 scope
|
|
28
|
+
|
|
29
|
+
This first iteration deliberately does **not** implement the 17
|
|
30
|
+
FAIRsFAIR/F-UJI metrics, a merged "FAIR score", or the metadata-enrichment
|
|
31
|
+
assistant described in the original design note. It covers only the
|
|
32
|
+
citation/impact-tracking half:
|
|
33
|
+
|
|
34
|
+
- Citation count (cited-by) per dataset.
|
|
35
|
+
- [Disruption index](https://doi.org/10.1038/s41586-019-0941-9) (Wu, Wang &
|
|
36
|
+
Evans, 2019): `DI = (N_F - N_B) / (N_F + N_B + N_R)`.
|
|
37
|
+
- S-index per ORCID: count of distinct researchers (deduplicated by ORCID,
|
|
38
|
+
falling back to normalised name) who authored a work citing any of that
|
|
39
|
+
researcher's FAIR3R datasets.
|
|
40
|
+
- Display on the dataset page (citation count, disruption index, S-index
|
|
41
|
+
with a co-author breakdown, a citing-works table).
|
|
42
|
+
|
|
43
|
+
## Why dedicated tables, not `package_extra`
|
|
44
|
+
|
|
45
|
+
Citations and scores are refreshed by a periodic batch job, not by the user
|
|
46
|
+
editing the dataset. Writing them through `package_update`/`package_extra`
|
|
47
|
+
would create a `package_revision` row and trigger a full Solr reindex for
|
|
48
|
+
every dataset on every refresh run - pure overhead for data nobody searches
|
|
49
|
+
on. Instead this plugin owns five Postgres tables of its own
|
|
50
|
+
(`fair_citation_stats`, `fair_citing_works`, `fair_citing_researchers`,
|
|
51
|
+
`fair_author_sindex`, `fair_score_history`), created via the same
|
|
52
|
+
Alembic-based migration mechanism as `ckanext-doi`
|
|
53
|
+
(`ckan db upgrade -p citations`).
|
|
54
|
+
|
|
55
|
+
`fair_citing_researchers` exists only to make the S-index's "unique
|
|
56
|
+
researchers" dedup correct across an author's several datasets: it's one
|
|
57
|
+
row per (FAIR3R author ORCID, citing-researcher identity) pair, and
|
|
58
|
+
`fair_author_sindex.s_index_current` is the cached `COUNT(DISTINCT ...)`
|
|
59
|
+
over it. Both the citation count and the S-index are also tracked as
|
|
60
|
+
high-water marks (`*_max`) so a researcher's score never visibly regresses
|
|
61
|
+
just because an external API temporarily deduplicates differently between
|
|
62
|
+
two refresh runs.
|
|
63
|
+
|
|
64
|
+
## The disruption index needs a reference list the dataset usually doesn't have
|
|
65
|
+
|
|
66
|
+
The Wu/Wang/Evans formula needs to know what the focal work's own
|
|
67
|
+
references are, to tell "citing works that also build on the same sources"
|
|
68
|
+
(`N_B`) apart from "citing works that only cite the focal work" (`N_F`).
|
|
69
|
+
OpenAlex rarely has a populated reference list for a dataset-type Work,
|
|
70
|
+
which would make `N_B`/`N_R` collapse to 0 and the index trivially tend to
|
|
71
|
+
`+1.0` for almost every dataset with any citations at all.
|
|
72
|
+
|
|
73
|
+
To get a meaningful signal, the reference set is enriched with the
|
|
74
|
+
dataset's own DataCite `relatedIdentifiers` whose `relationType` is
|
|
75
|
+
`References` or `Cites` (already supported by the FDF schema via
|
|
76
|
+
`ckanext-doi`) - each such DOI is resolved to an OpenAlex work id and added
|
|
77
|
+
to the focal work's reference set before classifying citing works.
|
|
78
|
+
|
|
79
|
+
## Config
|
|
80
|
+
|
|
81
|
+
```ini
|
|
82
|
+
# Contact email sent as OpenAlex's "polite pool" mailto param (better rate
|
|
83
|
+
# limits, not auth). Optional but recommended.
|
|
84
|
+
ckanext.citations.contact_email = your-team@example.org
|
|
85
|
+
|
|
86
|
+
# Pause between outbound API calls during a refresh, in seconds.
|
|
87
|
+
ckanext.citations.request_pause_seconds = 0.1
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Running a refresh
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
ckan citations refresh --min-age-days 7
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Idempotent and safe to run repeatedly: only datasets whose
|
|
97
|
+
`fair_citation_stats.last_checked` is older than `--min-age-days` (or
|
|
98
|
+
`NULL`, i.e. never checked) are touched. Intended to run from a weekly cron
|
|
99
|
+
job, same pattern as `fair3r update-schema`.
|
|
100
|
+
|
|
101
|
+
## Tests
|
|
102
|
+
|
|
103
|
+
Two `test.ini` files, same convention as the other FAIR3R extensions:
|
|
104
|
+
|
|
105
|
+
- `test.ini` at the repo root: Docker DEV (`/srv/app/src/ckan/test-core.ini`).
|
|
106
|
+
- `ckanext/citations/tests/test.ini`: shipped with the package, used by
|
|
107
|
+
`deploy/ckanext_test.py` on integration/validation (`/usr/lib/ckan/default/...`).
|
|
108
|
+
|
|
109
|
+
```shell
|
|
110
|
+
docker exec -u ckan -it ckan-app pytest --ckan-ini=/plugins/ckanext-citations/test.ini /plugins/ckanext-citations/ckanext/citations/tests
|
|
111
|
+
```
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+

|
|
2
|
+

|
|
3
|
+
|
|
4
|
+
# ckanext-citations
|
|
5
|
+
|
|
6
|
+
Citation tracking (cited-by, via OpenAlex with a DataCite Event Data
|
|
7
|
+
fallback), a per-dataset disruption index, and a per-researcher S-index
|
|
8
|
+
("how many unique researchers has your data enabled" - see
|
|
9
|
+
[s-index.science](https://s-index.science/)), displayed on the dataset
|
|
10
|
+
page.
|
|
11
|
+
|
|
12
|
+
## v1 scope
|
|
13
|
+
|
|
14
|
+
This first iteration deliberately does **not** implement the 17
|
|
15
|
+
FAIRsFAIR/F-UJI metrics, a merged "FAIR score", or the metadata-enrichment
|
|
16
|
+
assistant described in the original design note. It covers only the
|
|
17
|
+
citation/impact-tracking half:
|
|
18
|
+
|
|
19
|
+
- Citation count (cited-by) per dataset.
|
|
20
|
+
- [Disruption index](https://doi.org/10.1038/s41586-019-0941-9) (Wu, Wang &
|
|
21
|
+
Evans, 2019): `DI = (N_F - N_B) / (N_F + N_B + N_R)`.
|
|
22
|
+
- S-index per ORCID: count of distinct researchers (deduplicated by ORCID,
|
|
23
|
+
falling back to normalised name) who authored a work citing any of that
|
|
24
|
+
researcher's FAIR3R datasets.
|
|
25
|
+
- Display on the dataset page (citation count, disruption index, S-index
|
|
26
|
+
with a co-author breakdown, a citing-works table).
|
|
27
|
+
|
|
28
|
+
## Why dedicated tables, not `package_extra`
|
|
29
|
+
|
|
30
|
+
Citations and scores are refreshed by a periodic batch job, not by the user
|
|
31
|
+
editing the dataset. Writing them through `package_update`/`package_extra`
|
|
32
|
+
would create a `package_revision` row and trigger a full Solr reindex for
|
|
33
|
+
every dataset on every refresh run - pure overhead for data nobody searches
|
|
34
|
+
on. Instead this plugin owns five Postgres tables of its own
|
|
35
|
+
(`fair_citation_stats`, `fair_citing_works`, `fair_citing_researchers`,
|
|
36
|
+
`fair_author_sindex`, `fair_score_history`), created via the same
|
|
37
|
+
Alembic-based migration mechanism as `ckanext-doi`
|
|
38
|
+
(`ckan db upgrade -p citations`).
|
|
39
|
+
|
|
40
|
+
`fair_citing_researchers` exists only to make the S-index's "unique
|
|
41
|
+
researchers" dedup correct across an author's several datasets: it's one
|
|
42
|
+
row per (FAIR3R author ORCID, citing-researcher identity) pair, and
|
|
43
|
+
`fair_author_sindex.s_index_current` is the cached `COUNT(DISTINCT ...)`
|
|
44
|
+
over it. Both the citation count and the S-index are also tracked as
|
|
45
|
+
high-water marks (`*_max`) so a researcher's score never visibly regresses
|
|
46
|
+
just because an external API temporarily deduplicates differently between
|
|
47
|
+
two refresh runs.
|
|
48
|
+
|
|
49
|
+
## The disruption index needs a reference list the dataset usually doesn't have
|
|
50
|
+
|
|
51
|
+
The Wu/Wang/Evans formula needs to know what the focal work's own
|
|
52
|
+
references are, to tell "citing works that also build on the same sources"
|
|
53
|
+
(`N_B`) apart from "citing works that only cite the focal work" (`N_F`).
|
|
54
|
+
OpenAlex rarely has a populated reference list for a dataset-type Work,
|
|
55
|
+
which would make `N_B`/`N_R` collapse to 0 and the index trivially tend to
|
|
56
|
+
`+1.0` for almost every dataset with any citations at all.
|
|
57
|
+
|
|
58
|
+
To get a meaningful signal, the reference set is enriched with the
|
|
59
|
+
dataset's own DataCite `relatedIdentifiers` whose `relationType` is
|
|
60
|
+
`References` or `Cites` (already supported by the FDF schema via
|
|
61
|
+
`ckanext-doi`) - each such DOI is resolved to an OpenAlex work id and added
|
|
62
|
+
to the focal work's reference set before classifying citing works.
|
|
63
|
+
|
|
64
|
+
## Config
|
|
65
|
+
|
|
66
|
+
```ini
|
|
67
|
+
# Contact email sent as OpenAlex's "polite pool" mailto param (better rate
|
|
68
|
+
# limits, not auth). Optional but recommended.
|
|
69
|
+
ckanext.citations.contact_email = your-team@example.org
|
|
70
|
+
|
|
71
|
+
# Pause between outbound API calls during a refresh, in seconds.
|
|
72
|
+
ckanext.citations.request_pause_seconds = 0.1
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Running a refresh
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
ckan citations refresh --min-age-days 7
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Idempotent and safe to run repeatedly: only datasets whose
|
|
82
|
+
`fair_citation_stats.last_checked` is older than `--min-age-days` (or
|
|
83
|
+
`NULL`, i.e. never checked) are touched. Intended to run from a weekly cron
|
|
84
|
+
job, same pattern as `fair3r update-schema`.
|
|
85
|
+
|
|
86
|
+
## Tests
|
|
87
|
+
|
|
88
|
+
Two `test.ini` files, same convention as the other FAIR3R extensions:
|
|
89
|
+
|
|
90
|
+
- `test.ini` at the repo root: Docker DEV (`/srv/app/src/ckan/test-core.ini`).
|
|
91
|
+
- `ckanext/citations/tests/test.ini`: shipped with the package, used by
|
|
92
|
+
`deploy/ckanext_test.py` on integration/validation (`/usr/lib/ckan/default/...`).
|
|
93
|
+
|
|
94
|
+
```shell
|
|
95
|
+
docker exec -u ckan -it ckan-app pytest --ckan-ini=/plugins/ckanext-citations/test.ini /plugins/ckanext-citations/ckanext/citations/tests
|
|
96
|
+
```
|
|
File without changes
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
from datetime import datetime, timedelta, timezone
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from ckan.model import Package, Session
|
|
7
|
+
|
|
8
|
+
log = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
DEFAULT_MIN_AGE_DAYS = 7
|
|
11
|
+
DEFAULT_PAUSE_SECONDS = 0.1
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_commands():
|
|
15
|
+
return [citations]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@click.group()
|
|
19
|
+
def citations():
|
|
20
|
+
"""Citation/impact tracking commands."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@citations.command(name='refresh')
|
|
24
|
+
@click.option(
|
|
25
|
+
'--min-age-days',
|
|
26
|
+
default=DEFAULT_MIN_AGE_DAYS,
|
|
27
|
+
show_default=True,
|
|
28
|
+
help='Only refresh datasets whose citation stats were last checked more '
|
|
29
|
+
'than this many days ago (or never checked at all).',
|
|
30
|
+
)
|
|
31
|
+
@click.option(
|
|
32
|
+
'--limit',
|
|
33
|
+
default=None,
|
|
34
|
+
type=int,
|
|
35
|
+
help='Process at most this many datasets this run.',
|
|
36
|
+
)
|
|
37
|
+
@click.option(
|
|
38
|
+
'--pause-seconds',
|
|
39
|
+
default=DEFAULT_PAUSE_SECONDS,
|
|
40
|
+
show_default=True,
|
|
41
|
+
help='Pause between datasets, on top of the per-API-call pause already '
|
|
42
|
+
'applied inside each dataset refresh.',
|
|
43
|
+
)
|
|
44
|
+
def refresh(min_age_days, limit, pause_seconds):
|
|
45
|
+
"""Batched, idempotent citation refresh across every published-DOI
|
|
46
|
+
dataset due for a check. Safe to run on a cron - only datasets whose
|
|
47
|
+
last check is older than --min-age-days (or never checked) are touched.
|
|
48
|
+
"""
|
|
49
|
+
from ckanext.citations.lib.refresh import refresh_dataset
|
|
50
|
+
|
|
51
|
+
package_ids = _packages_due_for_refresh(min_age_days)
|
|
52
|
+
if limit:
|
|
53
|
+
package_ids = package_ids[:limit]
|
|
54
|
+
|
|
55
|
+
click.echo(f'{len(package_ids)} dataset(s) due for a citation refresh.')
|
|
56
|
+
|
|
57
|
+
for i, package_id in enumerate(package_ids, start=1):
|
|
58
|
+
try:
|
|
59
|
+
refresh_dataset(package_id)
|
|
60
|
+
click.echo(f'[{i}/{len(package_ids)}] refreshed {package_id}')
|
|
61
|
+
except Exception:
|
|
62
|
+
log.exception('Citation refresh failed for dataset %s', package_id)
|
|
63
|
+
click.secho(f'[{i}/{len(package_ids)}] FAILED {package_id}', fg='red')
|
|
64
|
+
Session.rollback()
|
|
65
|
+
time.sleep(pause_seconds)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _packages_due_for_refresh(min_age_days):
|
|
69
|
+
"""Queries ckan.model directly (not the package_search action) so this
|
|
70
|
+
maintenance job sees every dataset - public or private - and doesn't
|
|
71
|
+
depend on Solr being in sync."""
|
|
72
|
+
from ckanext.citations.model import CitationStats
|
|
73
|
+
|
|
74
|
+
cutoff = datetime.now(timezone.utc) - timedelta(days=min_age_days)
|
|
75
|
+
|
|
76
|
+
already_checked_recently = {
|
|
77
|
+
row.package_id
|
|
78
|
+
for row in Session.query(CitationStats.package_id).filter(
|
|
79
|
+
CitationStats.last_checked.isnot(None), CitationStats.last_checked > cutoff
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
all_active_ids = [
|
|
84
|
+
row.id
|
|
85
|
+
for row in Session.query(Package.id).filter(
|
|
86
|
+
Package.state == 'active', Package.type == 'dataset'
|
|
87
|
+
)
|
|
88
|
+
]
|
|
89
|
+
return [pid for pid in all_active_ids if pid not in already_checked_recently]
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Translations template for ckanext-citations.
|
|
2
|
+
# Copyright (C) 2026 ORGANIZATION
|
|
3
|
+
# This file is distributed under the same license as the ckanext-citations
|
|
4
|
+
# project.
|
|
5
|
+
# FIRST AUTHOR <EMAIL@ADDRESS>, 2026.
|
|
6
|
+
#
|
|
7
|
+
#, fuzzy
|
|
8
|
+
msgid ""
|
|
9
|
+
msgstr ""
|
|
10
|
+
"Project-Id-Version: ckanext-citations 0.1.6\n"
|
|
11
|
+
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
|
12
|
+
"POT-Creation-Date: 2026-10-05 15:53+0000\n"
|
|
13
|
+
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
|
14
|
+
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
15
|
+
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
16
|
+
"MIME-Version: 1.0\n"
|
|
17
|
+
"Content-Type: text/plain; charset=utf-8\n"
|
|
18
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
19
|
+
"Generated-By: Babel 2.18.0\n"
|
|
20
|
+
|
|
21
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:20
|
|
22
|
+
msgid "FAIR & Impact"
|
|
23
|
+
msgstr ""
|
|
24
|
+
|
|
25
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:29
|
|
26
|
+
msgid "Completeness"
|
|
27
|
+
msgstr ""
|
|
28
|
+
|
|
29
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:30
|
|
30
|
+
msgid ""
|
|
31
|
+
"How complete the FAIR3R metadata of this dataset is: the weighted share of "
|
|
32
|
+
"the fields that apply (required fields count double), CKAN basics included. "
|
|
33
|
+
"Conditional fields that are hidden are ignored."
|
|
34
|
+
msgstr ""
|
|
35
|
+
|
|
36
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:38
|
|
37
|
+
msgid "Missing fields"
|
|
38
|
+
msgstr ""
|
|
39
|
+
|
|
40
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:50
|
|
41
|
+
msgid "Citations"
|
|
42
|
+
msgstr ""
|
|
43
|
+
|
|
44
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:55
|
|
45
|
+
msgid "last checked"
|
|
46
|
+
msgstr ""
|
|
47
|
+
|
|
48
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:62
|
|
49
|
+
msgid "Disruption index"
|
|
50
|
+
msgstr ""
|
|
51
|
+
|
|
52
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:63
|
|
53
|
+
msgid ""
|
|
54
|
+
"Wu, Wang & Evans (2019). Between -1 and +1: close to +1 means the works "
|
|
55
|
+
"citing this dataset tend to replace it; close to -1 means they build on it "
|
|
56
|
+
"together with its own references. Uses the dataset References/Cites related "
|
|
57
|
+
"identifiers when present."
|
|
58
|
+
msgstr ""
|
|
59
|
+
|
|
60
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:69
|
|
61
|
+
msgid "not enough data yet"
|
|
62
|
+
msgstr ""
|
|
63
|
+
|
|
64
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:76
|
|
65
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:87
|
|
66
|
+
msgid "S-index"
|
|
67
|
+
msgstr ""
|
|
68
|
+
|
|
69
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:77
|
|
70
|
+
msgid ""
|
|
71
|
+
"Number of distinct researchers (identified by ORCID) who have cited at least "
|
|
72
|
+
"one of this author FAIR3R datasets. Shown for the highest co-author with an "
|
|
73
|
+
"ORCID."
|
|
74
|
+
msgstr ""
|
|
75
|
+
|
|
76
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:82
|
|
77
|
+
msgid "highest among co-authors"
|
|
78
|
+
msgstr ""
|
|
79
|
+
|
|
80
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:84
|
|
81
|
+
msgid "All co-authors"
|
|
82
|
+
msgstr ""
|
|
83
|
+
|
|
84
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:99
|
|
85
|
+
msgid "Not checked yet: citations are tracked once the dataset has a published DOI."
|
|
86
|
+
msgstr ""
|
|
87
|
+
|
|
88
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:103
|
|
89
|
+
msgid "Citing works"
|
|
90
|
+
msgstr ""
|
|
91
|
+
|
|
92
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:107
|
|
93
|
+
msgid "Title"
|
|
94
|
+
msgstr ""
|
|
95
|
+
|
|
96
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:108
|
|
97
|
+
msgid "Authors"
|
|
98
|
+
msgstr ""
|
|
99
|
+
|
|
100
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:109
|
|
101
|
+
msgid "Year"
|
|
102
|
+
msgstr ""
|
|
103
|
+
|
|
104
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:110
|
|
105
|
+
msgid "Venue"
|
|
106
|
+
msgstr ""
|
|
107
|
+
|
|
108
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:111
|
|
109
|
+
msgid "DOI"
|
|
110
|
+
msgstr ""
|
|
111
|
+
|
|
112
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:134
|
|
113
|
+
#, python-format
|
|
114
|
+
msgid "Show all %(count)s citing works"
|
|
115
|
+
msgstr ""
|
|
116
|
+
|
|
Binary file
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# French translations for ckanext-citations.
|
|
2
|
+
# Copyright (C) 2026 ORGANIZATION
|
|
3
|
+
# This file is distributed under the same license as the ckanext-citations
|
|
4
|
+
# project.
|
|
5
|
+
# FIRST AUTHOR <EMAIL@ADDRESS>, 2026.
|
|
6
|
+
#
|
|
7
|
+
msgid ""
|
|
8
|
+
msgstr ""
|
|
9
|
+
"Project-Id-Version: ckanext-citations 0.1.6\n"
|
|
10
|
+
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
|
11
|
+
"POT-Creation-Date: 2026-10-05 15:53+0000\n"
|
|
12
|
+
"PO-Revision-Date: 2026-10-05 15:53+0000\n"
|
|
13
|
+
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
14
|
+
"Language-Team: fr <LL@li.org>\n"
|
|
15
|
+
"Language: fr\n"
|
|
16
|
+
"MIME-Version: 1.0\n"
|
|
17
|
+
"Content-Type: text/plain; charset=utf-8\n"
|
|
18
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
19
|
+
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
|
20
|
+
"Generated-By: Babel 2.18.0\n"
|
|
21
|
+
|
|
22
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:20
|
|
23
|
+
msgid "FAIR & Impact"
|
|
24
|
+
msgstr "FAIR & Impact"
|
|
25
|
+
|
|
26
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:29
|
|
27
|
+
msgid "Completeness"
|
|
28
|
+
msgstr "Complétude"
|
|
29
|
+
|
|
30
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:30
|
|
31
|
+
msgid ""
|
|
32
|
+
"How complete the FAIR3R metadata of this dataset is: the weighted share of "
|
|
33
|
+
"the fields that apply (required fields count double), CKAN basics included. "
|
|
34
|
+
"Conditional fields that are hidden are ignored."
|
|
35
|
+
msgstr ""
|
|
36
|
+
"Niveau de complétude des métadonnées FAIR3R de ce dataset : part pondérée "
|
|
37
|
+
"des champs applicables (les champs obligatoires comptent double), en "
|
|
38
|
+
"incluant les champs de base de CKAN. Les champs conditionnels masqués sont "
|
|
39
|
+
"ignorés."
|
|
40
|
+
|
|
41
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:38
|
|
42
|
+
msgid "Missing fields"
|
|
43
|
+
msgstr "Champs manquants"
|
|
44
|
+
|
|
45
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:50
|
|
46
|
+
msgid "Citations"
|
|
47
|
+
msgstr "Citations"
|
|
48
|
+
|
|
49
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:55
|
|
50
|
+
msgid "last checked"
|
|
51
|
+
msgstr "dernière vérification"
|
|
52
|
+
|
|
53
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:62
|
|
54
|
+
msgid "Disruption index"
|
|
55
|
+
msgstr "Indice de disruption"
|
|
56
|
+
|
|
57
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:63
|
|
58
|
+
msgid ""
|
|
59
|
+
"Wu, Wang & Evans (2019). Between -1 and +1: close to +1 means the works "
|
|
60
|
+
"citing this dataset tend to replace it; close to -1 means they build on it "
|
|
61
|
+
"together with its own references. Uses the dataset References/Cites related "
|
|
62
|
+
"identifiers when present."
|
|
63
|
+
msgstr ""
|
|
64
|
+
"Wu, Wang & Evans (2019). Entre -1 et +1 : proche de +1, les œuvres citant ce"
|
|
65
|
+
" dataset tendent à le remplacer ; proche de -1, elles s'appuient aussi sur "
|
|
66
|
+
"ses références. Utilise les identifiants liés References/Cites du dataset "
|
|
67
|
+
"quand ils existent."
|
|
68
|
+
|
|
69
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:69
|
|
70
|
+
msgid "not enough data yet"
|
|
71
|
+
msgstr "pas assez de données pour l'instant"
|
|
72
|
+
|
|
73
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:76
|
|
74
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:87
|
|
75
|
+
msgid "S-index"
|
|
76
|
+
msgstr "S-index"
|
|
77
|
+
|
|
78
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:77
|
|
79
|
+
msgid ""
|
|
80
|
+
"Number of distinct researchers (identified by ORCID) who have cited at least"
|
|
81
|
+
" one of this author FAIR3R datasets. Shown for the highest co-author with an"
|
|
82
|
+
" ORCID."
|
|
83
|
+
msgstr ""
|
|
84
|
+
"Nombre de chercheurs distincts (identifiés par ORCID) ayant cité au moins un"
|
|
85
|
+
" des datasets FAIR3R de cet auteur. Affiché pour le co-auteur ayant le plus "
|
|
86
|
+
"haut score, parmi ceux qui ont un ORCID."
|
|
87
|
+
|
|
88
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:82
|
|
89
|
+
msgid "highest among co-authors"
|
|
90
|
+
msgstr "le plus élevé parmi les co-auteurs"
|
|
91
|
+
|
|
92
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:84
|
|
93
|
+
msgid "All co-authors"
|
|
94
|
+
msgstr "Tous les co-auteurs"
|
|
95
|
+
|
|
96
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:99
|
|
97
|
+
msgid ""
|
|
98
|
+
"Not checked yet: citations are tracked once the dataset has a published DOI."
|
|
99
|
+
msgstr ""
|
|
100
|
+
"Pas encore vérifié : les citations sont suivies une fois le dataset publié "
|
|
101
|
+
"avec un DOI."
|
|
102
|
+
|
|
103
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:103
|
|
104
|
+
msgid "Citing works"
|
|
105
|
+
msgstr "Œuvres citantes"
|
|
106
|
+
|
|
107
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:107
|
|
108
|
+
msgid "Title"
|
|
109
|
+
msgstr "Titre"
|
|
110
|
+
|
|
111
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:108
|
|
112
|
+
msgid "Authors"
|
|
113
|
+
msgstr "Auteurs"
|
|
114
|
+
|
|
115
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:109
|
|
116
|
+
msgid "Year"
|
|
117
|
+
msgstr "Année"
|
|
118
|
+
|
|
119
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:110
|
|
120
|
+
msgid "Venue"
|
|
121
|
+
msgstr "Revue"
|
|
122
|
+
|
|
123
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:111
|
|
124
|
+
msgid "DOI"
|
|
125
|
+
msgstr "DOI"
|
|
126
|
+
|
|
127
|
+
#: ckanext/citations/templates/package/snippets/citations_panel.html:134
|
|
128
|
+
#, python-format
|
|
129
|
+
msgid "Show all %(count)s citing works"
|
|
130
|
+
msgstr "Afficher les %(count)s œuvres citantes"
|
|
File without changes
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Fallback citation source: DataCite's Event Data API.
|
|
2
|
+
|
|
3
|
+
Used only to top up the citation count when OpenAlex hasn't indexed the
|
|
4
|
+
dataset's DOI at all. Event Data exposes bare subject/object DOI pairs, not
|
|
5
|
+
author/ORCID/reference-list metadata, so works discovered this way cannot
|
|
6
|
+
feed the disruption index or the S-index - they only contribute to
|
|
7
|
+
citation_count_current.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import time
|
|
12
|
+
|
|
13
|
+
import requests
|
|
14
|
+
|
|
15
|
+
log = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
BASE_URL = 'https://api.datacite.org/events'
|
|
18
|
+
REQUEST_TIMEOUT = 20
|
|
19
|
+
MAX_RETRIES = 4
|
|
20
|
+
INITIAL_BACKOFF_SECONDS = 1.0
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DataciteEventsError(Exception):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _get_with_backoff(params):
|
|
28
|
+
backoff = INITIAL_BACKOFF_SECONDS
|
|
29
|
+
last_error = None
|
|
30
|
+
for attempt in range(1, MAX_RETRIES + 1):
|
|
31
|
+
try:
|
|
32
|
+
resp = requests.get(BASE_URL, params=params, timeout=REQUEST_TIMEOUT)
|
|
33
|
+
except requests.RequestException as e:
|
|
34
|
+
last_error = f'DataCite Event Data request failed: {e}'
|
|
35
|
+
log.warning(
|
|
36
|
+
'%s (attempt %s/%s, retrying in %.1fs)',
|
|
37
|
+
last_error,
|
|
38
|
+
attempt,
|
|
39
|
+
MAX_RETRIES,
|
|
40
|
+
backoff,
|
|
41
|
+
)
|
|
42
|
+
time.sleep(backoff)
|
|
43
|
+
backoff *= 2.0
|
|
44
|
+
continue
|
|
45
|
+
|
|
46
|
+
if resp.status_code == 200:
|
|
47
|
+
return resp.json()
|
|
48
|
+
if resp.status_code == 429 or resp.status_code >= 500:
|
|
49
|
+
last_error = (
|
|
50
|
+
f'DataCite Event Data returned {resp.status_code}: {resp.text[:200]}'
|
|
51
|
+
)
|
|
52
|
+
log.warning(
|
|
53
|
+
'%s (attempt %s/%s, retrying in %.1fs)',
|
|
54
|
+
last_error,
|
|
55
|
+
attempt,
|
|
56
|
+
MAX_RETRIES,
|
|
57
|
+
backoff,
|
|
58
|
+
)
|
|
59
|
+
time.sleep(backoff)
|
|
60
|
+
backoff *= 2.0
|
|
61
|
+
continue
|
|
62
|
+
raise DataciteEventsError(
|
|
63
|
+
f'DataCite Event Data returned {resp.status_code}: {resp.text[:200]}'
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
raise DataciteEventsError(
|
|
67
|
+
f'DataCite Event Data request failed after {MAX_RETRIES} attempts: {last_error}'
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def get_citing_dois(doi):
|
|
72
|
+
"""Return the list of citing DOIs for `doi` via the is-cited-by relation.
|
|
73
|
+
Returns [] if DataCite has no events for this DOI (not an error - most
|
|
74
|
+
datasets simply won't have any yet)."""
|
|
75
|
+
params = {
|
|
76
|
+
'doi': doi,
|
|
77
|
+
'relation-type-id': 'is-cited-by',
|
|
78
|
+
'page[size]': 200,
|
|
79
|
+
}
|
|
80
|
+
data = _get_with_backoff(params)
|
|
81
|
+
citing_dois = []
|
|
82
|
+
for event in data.get('data', []):
|
|
83
|
+
attrs = event.get('attributes', {})
|
|
84
|
+
subj_id = attrs.get('subjId') or ''
|
|
85
|
+
# subjId is the citing work's DOI as a URL (https://doi.org/10.xxx/yyy).
|
|
86
|
+
if 'doi.org/' in subj_id:
|
|
87
|
+
citing_dois.append(subj_id.split('doi.org/', 1)[-1])
|
|
88
|
+
return citing_dois
|