aboutsummaryrefslogtreecommitdiffstats
path: root/python/fatcat/crossref_importer.py
blob: 3c6ba3ef4d49b56dd10932600fd28d9be217ce59 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183

import sys
import json
import sqlite3
import itertools
import fatcat_client
from fatcat.importer_common import FatcatImporter


class FatcatCrossrefImporter(FatcatImporter):

    def __init__(self, host_url, issn_map_file, extid_map_file=None, create_containers=True):
        super().__init__(host_url, issn_map_file)
        self.extid_map_db = None
        if extid_map_file:
            db_uri = "file:{}?mode=ro".format(extid_map_file)
            print("Using external ID map: {}".format(db_uri))
            self.extid_map_db = sqlite3.connect(db_uri, uri=True)
        else:
            print("Not using external ID map")
        self.create_containers = create_containers

    def lookup_ext_ids(self, doi):
        if self.extid_map_db is None:
            return dict(core_id=None, pmid=None, pmcid=None, wikidata_qid=None)
        row = self.extid_map_db.execute("SELECT core, pmid, pmcid, wikidata FROM ids WHERE doi=? LIMIT 1",
            [doi.lower()]).fetchone()
        if row is None:
            return dict(core_id=None, pmid=None, pmcid=None, wikidata_qid=None)
        row = [str(cell or '') or None for cell in row]
        return dict(
            core_id=row[0],
            pmid=row[1],
            pmcid=row[2],
            wikidata_qid=row[3])

    def parse_crossref_dict(self, obj):
        """
        obj is a python dict (parsed from json).
        returns a ReleaseEntity
        """

        # This work is out of scope if it doesn't have authors and a title
        if (not 'author' in obj) or (not 'title' in obj):
            return None

        # Other ways to be out of scope (provisionally)
        if ((not 'type' in obj) or (not 'container-title' in obj) or
                len(obj['container-title']) < 1):
            return None

        # contribs
        contribs = []
        for i, am in enumerate(obj['author']):
            creator_id = None
            if 'ORCID' in am.keys():
                creator_id = self.lookup_orcid(am['ORCID'].split('/')[-1])
            # Sorry humans :(
            if am.get('given') and am.get('family'):
                raw_name = "{} {}".format(am['given'], am['family'])
            elif am.get('family'):
                raw_name = am['family']
            else:
                # TODO: defaults back to a pseudo-null value
                raw_name = am.get('given', '<blank>')
            contribs.append(fatcat_client.ReleaseContrib(
                creator_id=creator_id,
                index=i+1,
                raw_name=raw_name,
                role="author"))

        # container
        issn = obj.get('ISSN', [None])[0]
        issnl = self.issn2issnl(issn)
        container_id = None
        if issnl:
            container_id = self.lookup_issnl(issnl)
        publisher = obj.get('publisher')

        ce = None
        if container_id is None and self.create_containers and issnl != None:
            ce = fatcat_client.ContainerEntity(
                issnl=issnl,
                publisher=publisher,
                name=obj['container-title'][0])

        # references
        refs = []
        for i, rm in enumerate(obj.get('reference', [])):
            try:
                year = int(rm.get('year'))
                # NOTE: will need to update/config in the future!
                # NOTE: are there crossref works with year < 100?
                if year > 2025 or year < 100:
                    year = None
            except:
                year = None
            extra = dict(crossref=rm)
            if rm.get('DOI') != None:
                extra['doi'] = rm.get('DOI').lower()
            refs.append(fatcat_client.ReleaseRef(
                index=i+1,
                # doing lookups would be a second import pass
                target_release_id=None,
                # unreliable for crossref: key=rm['key'].split('|')[-1],
                year=year,
                container_title=rm.get('volume-title'),
                title=rm.get('title'),
                locator=rm.get('first-page'),
                # TODO: just dump JSON somewhere here?
                extra=dict(crossref=rm)))

        # abstracts
        abstracts = []
        if obj.get('abstract') != None:
            abstracts.append(fatcat_client.ReleaseEntityAbstracts(
                mimetype="application/xml+jats",
                content=obj.get('abstract')))

        # release
        extra = dict(crossref={
            # TODO: if exsits: group_title, subtitle, isPreprintOf
            'links': obj.get('link', []),
            'subject': obj.get('subject'),
            'type': obj['type'],
            'license': obj.get('license', [dict(URL=None)])[0]['URL'] or None,
            'alternative-id': obj.get('alternative-id', [])})

        # external identifiers
        extids = self.lookup_ext_ids(doi=obj['DOI'].lower())

        re = fatcat_client.ReleaseEntity(
            work_id=None,
            title=obj['title'][0],
            contribs=contribs,
            refs=refs,
            container_id=container_id,
            release_type=obj['type'],
            doi=obj['DOI'].lower(),
            core_id=extids['core_id'],
            pmid=extids['pmid'],
            pmcid=extids['pmcid'],
            wikidata_qid=extids['wikidata_qid'],
            release_date=obj['created']['date-time'],
            issue=obj.get('issue'),
            volume=obj.get('volume'),
            pages=obj.get('page'),
            abstracts=abstracts,
            extra=extra)
        return (re, ce)

    def create_row(self, row, editgroup=None):
        if row is None:
            return
        obj = json.loads(row)
        entities = self.parse_crossref_dict(obj)
        if entities is not None:
            (re, ce) = entities
            if ce is not None:
                container = self.api.create_container(ce, editgroup=editgroup)
                re.container_id = container.ident
                self._issnl_id_map[ce.issnl] = container.ident
            self.api.create_release(re, editgroup=editgroup)
            self.insert_count = self.insert_count + 1

    def create_batch(self, batch, editgroup=None):
        """Current work/release pairing disallows batch creation of releases.
        Could do batch work creation and then match against releases, but meh."""
        release_batch = []
        for row in batch:
            if row is None:
                continue
            obj = json.loads(row)
            entities = self.parse_crossref_dict(obj)
            if entities is not None:
                (re, ce) = entities
                if ce is not None:
                    container = self.api.create_container(ce, editgroup=editgroup)
                    re.container_id = container.ident
                    self._issnl_id_map[ce.issnl] = container.ident
                release_batch.append(re)
        self.api.create_release_batch(release_batch, autoaccept="true", editgroup=editgroup)
        self.insert_count = self.insert_count + len(release_batch)