diff --git a/conditor-dumps/02-download.ini b/conditor-dumps/02-download.ini index 248d0f4..be8488e 100644 --- a/conditor-dumps/02-download.ini +++ b/conditor-dumps/02-download.ini @@ -1,80 +1,102 @@ -# npx ezs 02-download.ini +; npx ezs 02-download.ini append = pack - [use] plugin = basics plugin = conditor - [TXTConcat] - [replace] path = q value = self().trim() - [CORHALFetch] url = https://corhal-api.inist.fr retries = 3 timeout = 60000 [assign] -# Récupère electronicPublicationDate et publicationDate -# Prend la plus ancienne (= la plus petite) -# Ne garde que l'année -path = ApilPublicationDate -value = get("host.electronicPublicationDate", "9999") \ - .castArray() \ - .concat(_.get(self, "host.publicationDate", "9999")) \ - .min().toString() \ - .thru(str => str.substring(0,4)) +path = doi +value = get("doi","") + +path = abstract +value = get("abstract.default") + +path = title +value = get("title.default") + +path = halClassification +value = get("classifications.enrichments.hal.fr") + +path = keywordsAuthorsAndMesh +value = get("keywords.en").pick(["author", "mesh"]).values().flatten() + +path = conferenceDetails +value = fix([self.host.conference.name,self.host.conference.date,self.host.conference.place,self.host.conference.country]).compact().join(" ") + +path = volumeIssueAndPages +value = fix(`${self.host.volume} / ${self.host.issue} / ${self.host.pages.range}`).replace(/undefined/gmi,"") + +path = issn +value = get("host.issn","") + +path = eissn +value = get("host.eissn","") + +path = isbn +value = get("host.isbn","") + +path = language +value = get("host.language") + + +; Récupère electronicPublicationDate et publicationDate +; Prend la plus ancienne (= la plus petite) +; Ne garde que l'année +path = publicationYear +value = get("host.electronicPublicationDate", "9999").castArray().concat(_.get(self, "host.publicationDate", "9999")).min().toString().thru(str => str.substring(0,4)) [assign] -path = ApilFinancement -value = get('funders').castArray().filter(Boolean).thru(arr => Boolean(arr.length)) +path = isFunded +value = get("funders").castArray().filter(Boolean).size().thru(size => size > 0 ? "Oui" : "Non") -# Quand les RNSR ne sont pas fournis dans authors.*.affiliations.*.rnsr -# on utilise les enrichissements et on les met au même niveau dans ApilRnsr +path = funders +value = get("funders").map("fullname") +; Quand les RNSR ne sont pas fournis dans authors.*.affiliations.*.rnsr +; on utilise les enrichissements et on les met au même niveau dans codesRnsr [map] path = authors - [map/map] path = affiliations - [map/map/assign] path = ApilRnsr1 value = get("rnsr") - [map/map/swing] test = get("ApilRnsr1").isEmpty() [map/map/swing/assign] -path = ApilRnsr -value = get("enrichments.rnsr", []) \ - .filter(rnsr => !["200612821P", "200018571R", "199812965F", "201523784S"].includes(rnsr)) - -# On rassemble tous les RNSR au niveau de la notice (ceux en provenance de -# authors.*.rnsr et ceux en provenance de authors.*.affiliations.*) -# dans allAuthorsRnsr +path = codesRnsr +value = get("enrichments.rnsr", []).filter(rnsr => !["200612821P", "200018571R", "199812965F", "201523784S"].includes(rnsr)) +; On rassemble tous les RNSR au niveau de la notice (ceux en provenance de +; authors.*.rnsr et ceux en provenance de authors.*.affiliations.*) +; dans allAuthorsRnsr [assign] path=ApilRnsr2 value= get("authors").map("rnsr").flatten() - [assign] path = allApilRnsr1 ; value= get("authors").map("affiliations").map("ApilRnsr1") value= get("authors").flatMap("affiliations").flatMap("ApilRnsr1") - [assign] -path= ApilRnsr +path= codesRnsr value= get("allApilRnsr1").concat(self.ApilRnsr2).compact().uniq() - -# Garde un identifiant +; Garde un identifiant [assign] path = sourceUidChain value = get("business.sourceUidChain") - -#On ajoute un objet pour attribuer 'OA - Inconnu' aux null ou undefined +;On ajoute un objet pour attribuer 'OA - Inconnu' aux null ou undefined path = enrichments.openAccess.unpaywall.oaLocations value = get("enrichments.openAccess.unpaywall.oaLocations",[{"hostType":"OA - Inconnu"}]) -# Supprime les champs inutiles pour les études bibliométriques +;Réduction des objets authors pour ne conserver que les propriétés utiles +path=authors +value=get("authors").map(item => _.assign(_.pick(item, ["fullname", "rnsr"]),{ affiliations: item.affiliations.map(aff => _.omit(aff,["isni", "idRef","ref"])) })) +; Supprime les champs inutiles pour les études bibliométriques [exchange] -value = omit(['business','origins','technical','allApilRnsr1','ApilRnsr2']) +value = omit(["arxiv","inspire","articleNumber","business","origins","technical","allApilRnsr1","ApilRnsr2","keywords","classifications"]) \ No newline at end of file diff --git a/conditor-dumps/03-enrich.ini b/conditor-dumps/03-enrich.ini new file mode 100644 index 0000000..059d051 --- /dev/null +++ b/conditor-dumps/03-enrich.ini @@ -0,0 +1,313 @@ +append = pack +[use] +; URLConnect +plugin = basics +plugin = analytics +[env] +path = number2labelDR +value = fix({"01": "DR01 Ile-de-France Villejuif","02": "DR02 Paris-Centre","03": "DR01 Ile-de-France Villejuif","04": "DR04 Ile-de-France Gif-sur-Yvette","05": "DR05 Ile-de-France Meudon","06": "DR06 Centre Est","07": "DR07 Rhône Auvergne","08": "DR08 Centre Limousin Poitou Charente","10": "DR10 Alsace","11": "DR11 Alpes","12": "DR12 Provence et Corse","20": "DR20 Côte d'Azur","13": "DR13 Occitanie Est","14": "DR14 Occitanie Ouest","15": "DR15 Aquitaine","16": "DR16 Paris-Normandie","17": "DR17 Bretagne et Pays de la Loire","18": "DR18 Hauts-de-France","19": "DR16 Paris-Normandie"}) +path = weekNumber +value = thru(() => new Date()).thru(currentDate => Math.floor((currentDate - (new Date(currentDate.getFullYear(), 0, 1)))/(24 * 60 * 60 * 1000))).thru(days => Math.ceil(days / 7)) +path = dayNumber +value = thru(() => Math.floor((Date.now() - new Date(new Date().getFullYear(), 0, 0)) / 86400000)) +; Permet de choisir la durée du cache : +; - env("weekNumber") : permet de garder les données en cache pour une semaine, +; - env("dayNumber") : permet de garder les données en cache pour une journée, +; - forever : permet de garder les données en cache pour toujours +[env] +path = cacheSalt +value = env("weekNumber") + +[unpack] + +[assign] +path = wsHal +value = get("codesRnsr").thru(code=>_.isEmpty(code) ? self.doi : "") + +[swing] +test = get("wsHal").isEmpty() +reverse = true + +[swing/expand] +path = wsHal +size = 100 + +[swing/expand/URLConnect] +url = https://biblio-tools.services.istex.fr/v2/hal/works/expand +timeout = 90007 +noerror = true + +[assign] +path = retrieveHalRnsr +value = get("wsHal.text.back.listOrg").flatMap(item => (item && item.type === "structures") ? item.org : item).filter(org => org && org.type === "laboratory" && Array.isArray(org.idno)).flatMap(org => org.idno).filter(idno => idno.type === "RNSR").map(idno => idno._t) + +[assign] +path = codesRnsr +value = fix(self.codesRnsr,self.retrieveHalRnsr).flatten().compact() + +; Récupère les infos Loterre 2XK +[assign] +path = ws.loterre2xk +value = get("codesRnsr").castArray().map((itemcodesRnsr, indice) => ({indice, itemcodesRnsr, codeRNSR: itemcodesRnsr, institut: itemcodesRnsr, publicationDate: self.publicationYear })) + +[expand] +path = ws.loterre2xk + +[expand/exploding] + +[expand/expand] +path = value.itemcodesRnsr +size = 100 +cacheName = fix(`${env("cacheSalt")}-04-2xk-identify`) + +[expand/expand/URLConnect] +url = https://loterre-resolvers.services.istex.fr/v1/2XK/identify +timeout = 120000 +noerror = true + +[expand/assign] +path = value.institut +value = get("value.institut").append(`|${self.value.publicationDate}`) + +[expand/expand] +path = value.institut +size = 100 +file = ./03.1-enrich-rnsrByYear.ini +cacheName = fix(`${env("cacheSalt")}-04-rnsr-year-instituts-cnrs`) + +[expand/assign] +path = value.label +value = get("value.itemcodesRnsr.prefLabel@fr", "n/a") + +path = value.labelNormalized +value = get("value.itemcodesRnsr.prefLabel@fr", "n/a").thru(item => String(item).normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toUpperCase()) + +path = value.dr +value = get("value.itemcodesRnsr.delegationRegionale_dep").castArray().compact().map(n => _.get(env("number2labelDR"), n, `unknow ${n}` )) +[expand/aggregate] + +[assign] +path = codesRnsr +value = get("ws.loterre2xk").castArray().map("codeRNSR") + +path = laboIntitule +value = get("ws.loterre2xk").castArray().map("label") + +path = delegationsRegionales +value = get("ws.loterre2xk").castArray().flatMap("dr") + +path = institutCnrs +value = get("ws.loterre2xk").castArray().map("institut") + +; S'il y a au moins un institut, il y a au moins une affiliation CNRS +path = isCnrs +value = get("ws.loterre2xk").castArray().map("institut").size().thru(size => size > 0 ? "Oui" : "Non") +; [debug] +; path = codesRnsr +; path = laboIntitule +; path = delegationsRegionales +; path = institutCnrs +; path = isCnrs +[assign] +path=wsOpenalex +value = get("doi") + +[swing] +test = get("wsOpenalex").isEmpty() +reverse = true + +[swing/expand] +path = wsOpenalex +size = 100 + +[swing/expand/URLConnect] +url = https://biblio-tools.services.istex.fr/v1/openalex/works/expand +timeout = 90007 +noerror = true + +[assign] +path = isRetracted +value = get("wsOpenalex.is_retracted").thru(bool => bool === true ? "Oui" : "Non") + +[assign] +path = institutsPrincipaux +value = get("institutCnrs").flatMap(arr => arr.flatMap(item => item.split(";")).filter(instit => instit.includes("(P)")).map(instit => instit.replace(" (P)", ""))) + +[assign] +path = institutsSecondaires +value = get("institutCnrs").flatMap(arr => arr.flatMap(item => item.split(";")).filter(instit => instit.includes("(S)")).map(instit => instit.replace(" (S)", ""))) + +[assign] +path = oaStatusUnpaywall +value = get("enrichments.openAccess.unpaywall.oaStatus","OA - Inconnu").thru(status => status === "OA - Inconnu" ? status : _.capitalize(status)) + +[assign] +path = hostTypeUnpaywall +value = get("enrichments.openAccess.unpaywall.oaLocations").map("hostType").uniq().map(host => host === "repository" ? "Archive seule" : host === "publisher" ? "Editeur seul" : host==="OA - Inconnu" ? "Type d'accès inconnu" : host).thru(arr=>_.size(arr)===2 ? "Commun" : _.size(arr)===0 ? "OA - Non" : arr).toString() + +[assign] +path = isOaUnpaywall +value = get("enrichments.openAccess.unpaywall.isOa").thru(bool => bool === true ? "OA - Oui" : bool === false ? "OA - Non" : "OA - Inconnu") +; Données Open Access host type modifiées à partir d'un champ fulltext, si hal est présent +;Transformer des données inconnues de 'HostType' en repository si absence d'un DOI mais présence de Hal dans 'fulltext' +[assign] +path = oaLocationsUnpaywallHal +value = get("enrichments.openAccess.unpaywall.oaLocations").map("hostType").concat(self.fulltextUrl && self.fulltextUrl.includes("hal") ? ["repository"] : []).filter((value, index, collection) => !(value === "OA - Inconnu" && collection[index + 1] === "repository")).uniq() +;Transformer des données inconnues en "green" si absence d'un DOI mais présence de "repository" dans 'oaLocationsUnpaywallHal' +[assign] +path = oaStatusUnpaywallHal +value = get("enrichments.openAccess.unpaywall.oaStatus","OA - Inconnu").concat(self.oaLocationsUnpaywallHal.includes("repository") ? "repository" : []).reduce((acc, val, index, arr) => {return (["OA - Inconnu", "OA - Non", "closed"].includes(val) && arr.includes("repository"))? ["green"]: acc.concat(val)}, []).head().capitalize().replace("Oa - inconnu","OA - Inconnu") +;;Transformer des données inconnues en OA-Oui si absence d'un DOI mais présence de "green" dans 'oaStatusUnpaywallHal' +[assign] +path = isOaUnpaywallHal +value=get("oaStatusUnpaywallHal").thru(status=>status ==="OA - Inconnu" ? status : status ==="closed" ? "OA - Non" : "OA - Oui" ) + +[assign] +path = hostTypeUnpaywallHal +value = get("oaLocationsUnpaywallHal").map(host => host === "repository" ? "Archive seule" : host === "publisher" ? "Editeur seul" : host ==="OA - Inconnu" ? "Type d'accès inconnu" :host).thru(arr=>_.size(arr)===2 ? "Commun" : _.size(arr)===0 ? "OA - Non" : arr).toString() + +[assign] +path = oaStatusUnpaywallOpenalex +value=get("wsOpenalex.open_access/oa_status").thru(status => status==="diamond" ? "Diamond" : _.capitalize(self.enrichments.openAccess.unpaywall.oaStatus)).replace(/^$/,"OA - Inconnu") +;on crée un nouveau champ où l'on cumule les nouvelles données de 'oaStatusUnpaywallOpenalex' et 'oaStatusUnpaywallHal' +[assign] +path = oaStatusUnpaywallOpenalexHal +value = get("wsOpenalex.open_access/oa_status").thru(status => status ==="diamond" ? "Diamond" : self.oaStatusUnpaywallHal) +;Transformations spécifiques pour créer des valuers compatibles avec VEga-lite pour la création de graphiques +[assign] +path = graphSourceEditeurIsOa +value = get("enrichments.openAccess.unpaywall.isOa").replace(/^((?!Oui).)*$/,"null").prepend("OA=").append((";TypeAcces="+self.hostTypeUnpaywallHal).replace(/Commun|Editeur seul/g,"Editeur").replace(/OA - Non|Archive seule|OA - Inconnu/g,"null")) +[assign] +path = graphSourceEditeurIsOaHal +value = get("isOaUnpaywallHal").replace(/^((?!Oui).)*$/,"null").prepend("OA=").append((";TypeAcces="+self.hostTypeUnpaywallHal).replace(/Commun|Editeur seul/g,"Editeur").replace(/OA - Non|Archive seule|OA - Inconnu/g,"null")) +;On détermine l'ordre de provenance des notices composants la notice Conditor +[assign] +path = sourceOfMergedRecords +value = get("sourceUidChain").replace(/\$.*?!/g,"!").split("!").compact() + +;Récupère les fulltext d'unpaywall si le champs 'fulltexturl' (qui vient de conditor) est nul +[assign] +path = fulltextUrl +value = get("fulltextUrl").castArray().compact().thru(fulltxt => _.isEmpty(fulltxt) ? self.enrichments.openAccess.unpaywall.oaLocations.filter(item => item.hostType === "repository" && item.isBest===true).map(item => item.url) : fulltxt) + +;Homogénéise les types de document +[assign] +path = documentType +value = get("originalGenre").trim() +[expand] +path = documentType +size = 100 +file = ./03.2-enrich-docType.ini +cacheName = fix(`${env("cacheSalt")}-04-homogenize-document-type-json`) +; Si le WS renvoie un "n/a" +[swing] +test = get("documentType").isEqual("n/a") +; On l'écrase avec la valeur de "originalGenre" +[swing/assign] +path = documentType +value = get("originalGenre").trim() + +; Homogénéise les sources +[assign] +path = source +value = get("host.title",_.get(self,"host.conference.name")).trim() +; si les champs 'host.title' et 'host.conference.name' ne sont pas vides +[expand] +path = source +size = 100 +file = ./03.3-enrich-source.ini +cacheName = fix(`${env("cacheSalt")}-04-homogenize-source-json`) +[swing] +test = get("source").isEqual("n/a") +[swing/assign] +path = source +value = get("host.title",_.get(self,"host.conference.name")).trim().toUpper().thru(source => source === "N/A" || source === "" ? "NON RENSEIGNE" : source) + +; Homogénéise les éditeurs +[assign] +path = ws.racineDoiPublisher +value = get("doi").split("/").filter(i => i.match(/^10./)).pop() +[swing] +test = get("ws.racineDoiPublisher").isEmpty() +reverse = true +[swing/expand] +path = ws.racineDoiPublisher +size = 1 +cacheName = fix(`${env("cacheSalt")}-04-api-crossref-prefixes-expand`) +[swing/expand/URLFetch] +target = value +url = fix("https://api.crossref.org/prefixes/").append(self.value) +json = true +timeout = 60000 +noerror = true +retries = 2 +[Swing/expand/assign] +path = value +value = get("value.message.name", "n/a") + +; Dans un champ temporaire, récupérer la valeur host.publisher si elle est présente, sinon récupérer celle du WS DOI. +[assign] +path = tmp.publisher +value = get("host.publisher",_.get(self,"ws.racineDoiPublisher")) +; Homogénéise l'éditeur +[assign] +path = publisher +value = get("tmp.publisher","n/a").trim() +[expand] +path = publisher +size = 100 +file = ./03.4-enrich-publisher.ini +cacheName = fix(`${env("cacheSalt")}-04-homogenize-publisher-json`) +; Si host.publisher existe et que le publisher vaut n/a, +[swing] +test = has("host.publisher") +test = get("publisher").isEqual("n/a") +; On l'écrase avec la valeur de host.publisher +[swing/assign] +path = publisher +value = get("host.publisher") + +[assign] +path = publisher +value = get("publisher").toUpper().thru(pub => pub.match(/^SPRINGER.*$|.*NATURE PUBLISHING GROUP.*/i) ? "SPRINGER NATURE" : pub === "N/A" ? "NON RENSEIGNE" : pub) + +; Enrichissements pays +[assign] +path = ws.libpostal +value = get("authors").flatMap("affiliations").map("address").uniq().map((address, id) => ({id,value: address})) +[map] +path = ws.libpostal +[map/expand] +path = value +size = 100 +cacheName = fix(`${env("cacheSalt")}-04-address-expand`) +[map/expand/URLConnect] +url = https://affiliations-tools.services.istex.fr/v1/addresses/parse +timeout = 90007 +noerror = true +[map/expand/assign] +path = value.value.address +value = get("value.id") +path = value.value.country +value = get("value.value.country").replace(/\W/g, " ").trim() +[map/expand/assign] +path = value +value = get("value.value") +[map/expand/expand] +path = value.country +size = 10 +cacheName = fix(`${env("cacheSalt")}-04-country-expand`) +[map/expand/expand/URLConnect] +url = https://loterre-resolvers.services.istex.fr/v1/9SD/identify +timeout = 90008 +[map/exchange] +value = get("value") +; TODO: si champ state, on est aux États-Unis (United States of America) +[assign] +path = codeISO +value = get("ws.libpostal").castArray().filter(Boolean).map(n => n.country?.cartographyCode).uniq().filter(Boolean) +path = countries +value = get("ws.libpostal").castArray().filter(Boolean).map(n => n.country?.["prefLabel@en"]).uniq().filter(Boolean) +; Suppression des champs non voulus +[exchange] +value = omit(["wsHal","retrieveHalRnsr","tmp","ws","originalGenre","localRef","pii","host","wsOpenalex","enrichments","oaLocationsUnpaywallHal"]) \ No newline at end of file diff --git a/conditor-dumps/03-enrich/.gitkeep b/conditor-dumps/03-enrich/.gitkeep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/conditor-dumps/03-enrich/.gitkeep diff --git a/conditor-dumps/03.1-enrich-rnsrByYear.ini b/conditor-dumps/03.1-enrich-rnsrByYear.ini new file mode 100644 index 0000000..760ad9c --- /dev/null +++ b/conditor-dumps/03.1-enrich-rnsrByYear.ini @@ -0,0 +1,36 @@ +# Création d'une variable d'environnement qui stocke la date au format ISO +[env] +path = date +value = thru(d => (new Date()).toISOString().split("T")[0]) + +# Sert à traiter individuellement chaque valeur de la liste (chaque code ici) +[map] +path = value +# Création d'une clé temporaire pour stocker la valeur actuelle +[map/replace] +path = tmpkey +value = self() + +[map/combine] +path = tmpkey +# URL vers un fichier CSV accessible via internet +primer = http://mapping-tables.daf.intra.inist.fr/rnsr-year-instituts-cnrs.tsv +cacheName = rnsrByYear +default = n/a + +[map/combine/URLStream] +path = false + +[map/combine/CSVParse] +separator = fix("\t") + +[map/combine/CSVObject] +# Une fois le TSV parsé en objets JSON, la valeur de "From" est transformé en clé et celle de "To" en valeur ({"From": "code1", "To": "Intitulé1"}). Puis on transforme l'objet avec "intitule" comme clé. +[map/combine/replace] +path = id +value = get('From') +path = value +value = get('To').split().zipObject(["intitule"]).invert() +# Enfin on extrait uniquement la vaeur de "intitule" après le mapping +[map/exchange] +value = get('tmpkey.value.intitule') diff --git a/conditor-dumps/03.2-enrich-docType.ini b/conditor-dumps/03.2-enrich-docType.ini new file mode 100644 index 0000000..7b4da58 --- /dev/null +++ b/conditor-dumps/03.2-enrich-docType.ini @@ -0,0 +1,26 @@ +[assign] +path = value +value=get("value") + +[combine] +path = value +primer = http://mapping-tables.daf.intra.inist.fr/homogenize-documenttype-json.tsv +cacheName = documentType + +[combine/URLStream] +path = false + +[combine/CSVParse] +separator = fix("\t") + +[combine/CSVObject] + +[combine/replace] +path=id +value=get('From') +path = value +value = get('To') + +[assign] +path=value +value=get("value.value") \ No newline at end of file diff --git a/conditor-dumps/03.3-enrich-source.ini b/conditor-dumps/03.3-enrich-source.ini new file mode 100644 index 0000000..ed35563 --- /dev/null +++ b/conditor-dumps/03.3-enrich-source.ini @@ -0,0 +1,27 @@ +[assign] +path = value +value=get("value") + +[combine] +path = value +primer = http://mapping-tables.daf.intra.inist.fr/homogenize-source-json.tsv +cacheName = sources +default = n/a + +[combine/URLStream] +path = false + +[combine/CSVParse] +separator = fix("\t") + +[combine/CSVObject] + +[combine/replace] +path=id +value=get('From') +path = value +value = get('To') + +[assign] +path=value +value=get("value.value") \ No newline at end of file diff --git a/conditor-dumps/03.4-enrich-publisher.ini b/conditor-dumps/03.4-enrich-publisher.ini new file mode 100644 index 0000000..7229f9a --- /dev/null +++ b/conditor-dumps/03.4-enrich-publisher.ini @@ -0,0 +1,26 @@ +[assign] +path = value +value=get("value") + +[combine] +path = value +primer = http://mapping-tables.daf.intra.inist.fr/homogenize-publisher-json.tsv +cacheName = publishers + +[combine/URLStream] +path = false + +[combine/CSVParse] +separator = fix("\t") + +[combine/CSVObject] + +[combine/replace] +path=id +value=get('From') +path = value +value = get('To') + +[assign] +path=value +value=get("value.value") \ No newline at end of file diff --git a/conditor-dumps/04-enrich-rnsrByYear.ini b/conditor-dumps/04-enrich-rnsrByYear.ini deleted file mode 100644 index 2ed3442..0000000 --- a/conditor-dumps/04-enrich-rnsrByYear.ini +++ /dev/null @@ -1,38 +0,0 @@ -# Création d'une variable d'environnement qui stocke la date au format ISO -[env] -path = date -value = thru(d => (new Date()).toISOString().split("T")[0]) - -# Sert à traiter individuellement chaque valeur de la liste (chaque code ici) -[map] -path = value -# Création d'une clé temporaire pour stocker la valeur actuelle -[map/replace] -path = tmpkey -value = self() - -[map/combine] -path = tmpkey -# URL vers un fichier CSV accessible via internet -primer = http://mapping-tables.daf.intra.inist.fr/rnsr-year-instituts-cnrs.tsv - -# Création d'un fichier temporaire local avec la date définie dans [env] (facultatif, évite de télécharger le fichier plusieurs fois) -cacheName = env("date").prepend("yo") -default = n/a - -[map/combine/URLStream] -path = false - -[map/combine/CSVParse] -separator = fix("\t") - -[map/combine/CSVObject] -# Une fois le TSV parsé en objets JSON, la valeur de "From" est transformé en clé et celle de "To" en valeur ({"From": "code1", "To": "Intitulé1"}). Puis on transforme l'objet avec "intitule" comme clé. -[map/combine/replace] -path = id -value = get('From') -path = value -value = get('To').split().zipObject(["intitule"]).invert() -# Enfin on extrait uniquement la vaeur de "intitule" après le mapping -[map/exchange] -value = get('tmpkey.value.intitule') diff --git a/conditor-dumps/04-enrich.ini b/conditor-dumps/04-enrich.ini index aa71070..f673c23 100644 --- a/conditor-dumps/04-enrich.ini +++ b/conditor-dumps/04-enrich.ini @@ -1,20 +1,15 @@ append = pack - [use] # URLConnect plugin = basics plugin = analytics - [env] path = number2labelDR -value = fix({"01": "DR01 Ile-de-France Villejuif","02": "DR02 Paris-Centre","04": "DR04 Ile-de-France Gif-sur-Yvette","05": "DR05 Ile-de-France Meudon","16": "DR16 Paris-Normandie","06": "DR06 Centre Est","10": "DR10 Alsace","08": "DR08 Centre Limousin Poitou Charente","17": "DR17 Bretagne et Pays de la Loire","18": "DR18 Hauts-de-France","07": "DR07 Rhône Auvergne","11": "DR11 Alpes","12": "DR12 Provence et Corse","20": "DR20 Côte d'Azur","13": "DR13 Occitanie Est","14": "DR14 Occitanie Ouest","15": "DR15 Aquitaine"}) - +value = fix({"01": "DR01 Ile-de-France Villejuif","02": "DR02 Paris-Centre","03": "DR01 Ile-de-France Villejuif","04": "DR04 Ile-de-France Gif-sur-Yvette","05": "DR05 Ile-de-France Meudon","06": "DR06 Centre Est","07": "DR07 Rhône Auvergne","08": "DR08 Centre Limousin Poitou Charente","10": "DR10 Alsace","11": "DR11 Alpes","12": "DR12 Provence et Corse","20": "DR20 Côte d'Azur","13": "DR13 Occitanie Est","14": "DR14 Occitanie Ouest","15": "DR15 Aquitaine","16": "DR16 Paris-Normandie","17": "DR17 Bretagne et Pays de la Loire","18": "DR18 Hauts-de-France","19": "DR16 Paris-Normandie"}) path = weekNumber value = thru(() => new Date()).thru(currentDate => Math.floor((currentDate - (new Date(currentDate.getFullYear(), 0, 1)))/(24 * 60 * 60 * 1000))).thru(days => Math.ceil(days / 7)) - path = dayNumber value = thru(() => Math.floor((Date.now() - new Date(new Date().getFullYear(), 0, 0)) / 86400000)) - # Permet de choisir la durée du cache : # - env('weekNumber') : permet de garder les données en cache pour une semaine, # - env('dayNumber') : permet de garder les données en cache pour une journée, @@ -31,7 +26,7 @@ # Récupère les infos Loterre 2XK [assign] path = ws.loterre2xk -value = get("ApilRnsr").castArray().map((itemApilRnsr, indice) => ({indice, itemApilRnsr, codeRNSR: itemApilRnsr, institut: itemApilRnsr, publicationDate: self.ApilPublicationDate })) +value = get("codesRnsr").castArray().map((itemcodesRnsr, indice) => ({indice, itemcodesRnsr, codeRNSR: itemcodesRnsr, institut: itemcodesRnsr, publicationDate: self.publicationYear })) [expand] path = ws.loterre2xk @@ -39,7 +34,7 @@ [expand/exploding] [expand/expand] -path = value.itemApilRnsr +path = value.itemcodesRnsr size = 100 cacheName = fix(`${env('cacheSalt')}-04-2xk-identify`) @@ -61,183 +56,148 @@ [expand/assign] path = value.label -value = get('value.itemApilRnsr.prefLabel@fr', 'n/a') +value = get('value.itemcodesRnsr.prefLabel@fr', 'n/a') path = value.labelNormalized -value = get('value.itemApilRnsr.prefLabel@fr', 'n/a').thru(item => String(item).normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toUpperCase()) +value = get('value.itemcodesRnsr.prefLabel@fr', 'n/a').thru(item => String(item).normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toUpperCase()) path = value.dr -value = get("value.itemApilRnsr.delegationRegionale_dep").castArray().compact().map(n => _.get(env("number2labelDR"), n, `unknow ${n}` )) +value = get("value.itemcodesRnsr.delegationRegionale_dep").castArray().compact().map(n => _.get(env("number2labelDR"), n, `unknow ${n}` )) [expand/aggregate] [assign] -path = ApilRnsr +path = codesRnsr value = get("ws.loterre2xk").castArray().map('codeRNSR') -path = ApilWsLaboIntitule +path = laboIntitule value = get("ws.loterre2xk").castArray().map('label') -path = ApilWsDr +path = delegationsRegionales value = get("ws.loterre2xk").castArray().map('dr') -path = ApilWsInstitutCnrs +path = institutCnrs value = get("ws.loterre2xk").castArray().map('institut') # S'il y a au moins un institut, il y a au moins une affiliation CNRS -path = ApilWsIsCnrs +path = isCnrs value = get("ws.loterre2xk").castArray().map('institut').thru(array => Boolean(array.length)) ; [debug] -; path = ApilRnsr -; path = ApilWsLaboIntitule -; path = ApilWsDr -; path = ApilWsInstitutCnrs -; path = ApilWsIsCnrs - -############################################################### - -#interrogation d'Openalex depuis le champs doi https://biblio-tools.services.istex.fr/v1/openalex/works/expand -#Pas d'omit sur ce champs, les valeurs apc peuvent eventuellement servir pour d'autres cas +; path = codesRnsr +; path = laboIntitule +; path = delegationsRegionales +; path = institutCnrs +; path = isCnrs [assign] -path=ApilWsOpenalex +path=wsOpenalex value = get("doi") -[expand] -path = ApilWsOpenalex +[swing] +test = get("wsOpenalex").isEmpty() +reverse = true + +[swing/expand] +path = wsOpenalex size = 100 -[expand/URLConnect] +[swing/expand/URLConnect] url = https://biblio-tools.services.istex.fr/v1/openalex/works/expand timeout = 90007 noerror = true - # Données Open Access host type modifiées à partir d'un champ fulltext, si hal est présent #Transformer des données inconnues de 'HostType' en repository si absence d'un DOI mais présence de Hal dans 'fulltext' -[assign] -path=ApilOaLocationsHal -value=get("enrichments.openAccess.unpaywall.oaLocations").map("hostType").concat([self.fulltextUrl].map((value)=>value && value.replace(/^((?!hal).)*$/,"@@@@").replace(/.*hal.*/,"repository"))).uniq().filter((value, index, collection)=>{if(!(value === "OA - Inconnu" && collection[index+1] === "repository" )){return true}}).filter(value=>value!=="@@@@").compact() -#Transformer des données inconnues en "green" si absence d'un DOI mais présence de "repository" dans 'ApilOaLocationsHal' [assign] -path=ApilOaStatusHal -value=get("enrichments.openAccess.unpaywall.oaStatus").replace(/^$/,"OA - Inconnu").castArray().concat(self.ApilOaLocationsHal).compact().join(",").replace(/OA - Inconnu,repository|OA - Non,repository|closed,repository/g,"green").split(",").head().capitalize().replace("Oa - inconnu","OA - Inconnu") +path = is_retracted +value = get("wsOpenalex.is_retracted").thru(bool => bool === true ? "Oui" : "Non") -##Transformer des données inconnues en OA-Oui si absence d'un DOI mais présence de "green" dans 'ApiloaStatusHal' [assign] -path=ApilIsOaHal -value=get("ApilOaStatusHal","OA - Inconnu").replace("closed","OA - Non").replace(/^((?!OA).)*$/,"OA - Oui") +path = oaLocationsUnpaywallHal +value = get("enrichments.openAccess.unpaywall.oaLocations").map("hostType").concat(self.fulltextUrl && self.fulltextUrl.includes("hal") ? ["repository"] : []).filter((value, index, collection) => !(value === "OA - Inconnu" && collection[index + 1] === "repository")).uniq() +#Transformer des données inconnues en "green" si absence d'un DOI mais présence de "repository" dans 'oaLocationsUnpaywallHal' +[assign] +path = oaStatusUnpaywallHal +value = get("enrichments.openAccess.unpaywall.oaStatus","OA - Inconnu").concat(self.oaLocationsUnpaywallHal.includes("repository") ? "repository" : []).reduce((acc, val, index, arr) => {return (["OA - Inconnu", "OA - Non", "closed"].includes(val) && arr.includes("repository"))? ["green"]: acc.concat(val)}, []).head().capitalize().replace("Oa - inconnu","OA - Inconnu") +##Transformer des données inconnues en OA-Oui si absence d'un DOI mais présence de "green" dans 'oaStatusUnpaywallHal' +[assign] +path = isOaUnpaywallHal +value=get("oaStatusUnpaywallHal").thru(v=>v ==="OA - Inconnu" ? "OA - Inconnu" : v==="closed" ? "OA - Non" : "OA - Oui" ) #On traduit les voies d'acces. Sort pour placer "publisher" avant "repository", replace puis si les 2 valeurs sont présentes, on remplace par "commun" [assign] -path=ApilTypeDaccesHal -value=get("ApilOaLocationsHal").sort().replace("repository","Archive seule").replace("publisher","Editeur seul").replace("Editeur seul,Archive seule","Commun") - +path = hostTypeUnpaywallHal +value = get("oaLocationsUnpaywallHal").map(v => v === "repository" ? "Archive seule" : v === "publisher" ? "Editeur seul" : v==="OA - Inconnu" ? v :v).thru(arr=>_.size(arr)===2 ? "Commun" : _.size(arr)===0 ? "OA - Non" : arr).toString() #on crée un nouveau champ où l'on récupère les valeurs de "apc_list/value". Si value = 0 alors la publi est diamant. On remplace donc "0" par "diamond" et efface tout le reste. On concatène ensuite avec enrichments/openAccess/unpaywall/oaStatus qui donne les couleurs de l'OA. Puis on retire "gold" lorsqu'il est associé à "diamond" [assign] -path=ApilOaStatusDiamond -value=get("ApilWsOpenalex").castArray().map("apc_list/value").replace(/^(?!0$).*$/,"").replace(/^0$/,"diamond").concat(_.get(self,"enrichments.openAccess.unpaywall.oaStatus")).filter((value, index, collection)=>{if(!(value === "gold" && collection[index-1] === "diamond" )){return true}}).last().capitalize().replace(/^$/,"OA - Inconnu") - - -#on crée un nouveau champ où l'on cumule les nouvelles données de 'ApilOaStatusDiamond' et 'ApilOaStatusHal' +path = oaStatusUnpaywallOpenalex +value=get("wsOpenalex.open_access/oa_status").thru(v=>v==="diamond" ? "Diamond" : _.capitalize(self.enrichments.openAccess.unpaywall.oaStatus)).replace(/^$/,"OA - Inconnu") +#on crée un nouveau champ où l'on cumule les nouvelles données de 'oaStatusUnpaywallOpenalex' et 'oaStatusUnpaywallHal' [assign] -path=ApilOaStatusDiamondHal -value=get("ApilOaStatusDiamond").concat(self.ApilOaStatusHal).uniq().filter((value, index, collection)=>{if(!(value === "OA - Inconnu" && collection[index+1] === "Green" )){return true}}).filter((value, index, collection)=>{if(!(value === "Gold" && collection[index-1] === "Diamond" )){return true}}).toString() - +path = oaStatusUnpaywallOpenalexHal +value = get("wsOpenalex.open_access/oa_status").thru(v=>v==="diamond" ? "Diamond" : self.oaStatusUnpaywallHal) #Transformations spécifiques pour créer des valuers compatibles avec VEga-lite pour la création de graphiques [assign] -path=ApilGraphSourceEditeurIsOa -value=get("enrichments.openAccess.unpaywall.isOa").replace(/^((?!Oui).)*$/,"null").prepend("OA=").append((";TypeAcces="+self.ApilTypeDaccesHal).replace(/Commun|Editeur seul/g,"Editeur").replace(/OA - Non|Archive seule|OA - Inconnu/g,"null")) - +path = graphSourceEditeurIsOa +value = get("enrichments.openAccess.unpaywall.isOa").replace(/^((?!Oui).)*$/,"null").prepend("OA=").append((";TypeAcces="+self.hostTypeUnpaywallHal).replace(/Commun|Editeur seul/g,"Editeur").replace(/OA - Non|Archive seule|OA - Inconnu/g,"null")) [assign] -path=ApilGraphSourceEditeurIsOaHal -value=get("ApilIsOaHal").replace(/^((?!Oui).)*$/,"null").prepend("OA=").append((";TypeAcces="+self.ApilTypeDaccesHal).replace(/Commun|Editeur seul/g,"Editeur").replace(/OA - Non|Archive seule|OA - Inconnu/g,"null")) - +path = graphSourceEditeurIsOaHal +value = get("isOaUnpaywallHal").replace(/^((?!Oui).)*$/,"null").prepend("OA=").append((";TypeAcces="+self.hostTypeUnpaywallHal).replace(/Commun|Editeur seul/g,"Editeur").replace(/OA - Non|Archive seule|OA - Inconnu/g,"null")) #On détermine l'ordre de provenance des notices composants la notice Conditor [assign] -path = ApilProvenance +path = sourceOfMergedRecords value = get("sourceUidChain").replace(/\$.*?!/g,"!").split("!").compact() - #On concatène 'volume', 'issue' et 'pages.range' dans un seul champ -[assign] -path = ApilCollation -value = get("host.volume").concat(_.get(self,"host.issue")).concat(_.get(self,"host.pages.range")).join(" / ") -#Récupère les fulltext d'unpaywall si le champs 'fulltexturl' (qui vient de conditor) est nul dans une colonne nommée ApilFullText (je ne sais pas comment on déclare le nom de la colonne dans ce cas précis) - +#Récupère les fulltext d'unpaywall si le champs 'fulltexturl' (qui vient de conditor) est nul [assign] path = fulltextUrl -value = get("fulltextUrl").castArray().compact() - -[swing] -test = get("fulltextUrl").isEmpty() - -[swing/assign] -path = fulltextUrl -value=get("enrichments.openAccess.unpaywall.oaLocations").filter(item=>item.hostType==="repository").map(item=>item.url) - -; [assign] -; path = value -; value = get('fulltextUrl').castArray().uniq() +value = get("fulltextUrl").castArray().compact().thru(v => _.isEmpty(v) ? self.enrichments.openAccess.unpaywall.oaLocations.filter(item => item.hostType === "repository" && item.isBest===true).map(item => item.url) : v) #Homogénéise les types de document [assign] -path = ApilWsTypeDoc +path = documentType value = get("originalGenre").trim() - [expand] -path = ApilWsTypeDoc +path = documentType size = 100 +file = ./04-enrich-doctype.ini cacheName = fix(`${env('cacheSalt')}-04-homogenize-document-type-json`) - -[expand/URLConnect] -url = https://mapping-tools.services.istex.fr/v1/homogenize/documentType/json -timeout = 90003 -noerror = true - # Si le WS renvoie un "n/a" [swing] -test = get("ApilWsTypeDoc").isEqual("n/a") - +test = get("documentType").isEqual("n/a") # On l'écrase avec la valeur de "originalGenre" [swing/assign] -path = ApilWsTypeDoc +path = documentType value = get("originalGenre").trim() - # Homogénéise les sources -[assign] -path = ApilWsSource -value = get("host.title",_.get(self,"host.conference.name")).trim() + +[assign] +path = source +value = get("host.title",_.get(self,"host.conference.name")).trim() # si les champs 'host.title' et 'host.conference.name' ne sont pas vides [expand] -path = ApilWsSource +path = source size = 100 +file = ./04-enrich-source.ini cacheName = fix(`${env('cacheSalt')}-04-homogenize-source-json`) - -[expand/URLConnect] -url = https://mapping-tools.services.istex.fr/v1/homogenize/source/json -timeout = 90004 -noerror = true - -# si le champ "ApilWsSource" issu du WS est "n/a" [swing] -test = get("ApilWsSource").isEqual("n/a") - +test = get("source").isEqual("n/a") [swing/assign] -path = ApilWsSource +path = source value = get("host.title",_.get(self,"host.conference.name")).trim() + +# si le champ "source" issu du WS est "n/a" # Traitement des éditeurs [assign] path = ws.ApilRacineDoiPublisher value = get("doi").split('/').filter(i => i.match(/^10./)).pop() - [expand] path = ws.ApilRacineDoiPublisher size = 1 cacheName = fix(`${env('cacheSalt')}-04-api-crossref-prefixes-expand`) - [expand/URLFetch] target = value url = fix('https://api.crossref.org/prefixes/').append(self.value) @@ -245,101 +205,80 @@ timeout = 60000 noerror = true retries = 2 - [expand/assign] path = value value = get('value.message.name', 'n/a') - # Dans un champ temporaire, récupérer la valeur host.publisher si elle est présente, sinon récupérer celle du WS DOI. [assign] -path = tmp.ApilWsPublisher +path = tmp.publisher value = get("host.publisher",_.get(self,"ws.ApilRacineDoiPublisher")) - # Homogénéise l'éditeur [assign] -path = ApilWsPublisher -value = get("tmp.ApilWsPublisher").trim() - +path = publisher +value = get("tmp.publisher").trim() [expand] -path = ApilWsPublisher +path = publisher size = 100 +file = ./04-enrich-publisher.ini cacheName = fix(`${env('cacheSalt')}-04-homogenize-publisher-json`) -[expand/URLConnect] -url = https://mapping-tools.services.istex.fr/v1/homogenize/publisher/json -timeout = 90006 -noerror = true -# Si host.publisher existe et que le ApilWsPublisher vaut n/a, +# Si host.publisher existe et que le publisher vaut n/a, [swing] test = has("host.publisher") -test = get("ApilWsPublisher").isEqual("n/a") - +test = get("publisher").isEqual("n/a") # On l'écrase avec la valeur de host.publisher [swing/assign] -path = ApilWsPublisher +path = publisher value = get("host.publisher") - # Enrichissements pays [assign] path = ws.libpostal value = get("authors") \ - .flatMap("affiliations") \ - .map("address").uniq() \ - .map((address, id) => ({ \ - id, \ - value: address \ - })) - +.flatMap("affiliations") \ +.map("address").uniq() \ +.map((address, id) => ({ \ +id, \ +value: address \ +})) [map] path = ws.libpostal - [map/expand] path = value size = 100 -cacheName = fix(`${env('cacheSalt')}-04-address-expand}`) - +cacheName = fix(`${env('cacheSalt')}-04-address-expand`) [map/expand/URLConnect] url = https://affiliations-tools.services.istex.fr/v1/addresses/parse timeout = 90007 noerror = true - [map/expand/assign] path = value.value.address value = get('value.id') - path = value.value.country value = get('value.value.country').replace(/\W/g, ' ').trim() - [map/expand/assign] path = value value = get('value.value') - [map/expand/expand] path = value.country size = 10 cacheName = fix(`${env('cacheSalt')}-04-country-expand`) - [map/expand/expand/URLConnect] url = https://loterre-resolvers.services.istex.fr/v1/9SD/identify timeout = 90008 - [map/exchange] value = get('value') - # TODO: si champ state, on est aux États-Unis (United States of America) - [assign] -path = ApilWsCodeISO +path = codeISO value = get("ws.libpostal").castArray().filter(Boolean) \ - .map(n => n.country?.cartographyCode) \ - .uniq().filter(Boolean) - -path = ApilWsCountry +.map(n => n.country?.cartographyCode) \ +.uniq().filter(Boolean) +path = countries value = get("ws.libpostal").castArray().filter(Boolean) \ - .map(n => n.country?.["prefLabel@en"]) \ - .uniq().filter(Boolean) - +.map(n => n.country?.["prefLabel@en"]) \ +.uniq().filter(Boolean) # Suppression des champs non voulus [exchange] -value = omit(['tmp']) +value = omit(['tmp','ws','originalGenre','localRef','pii','host']) + diff --git a/conditor-dumps/04-report/.gitkeep b/conditor-dumps/04-report/.gitkeep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/conditor-dumps/04-report/.gitkeep diff --git a/conditor-dumps/Makefile b/conditor-dumps/Makefile index 4c6a8cc..886e0d7 100644 --- a/conditor-dumps/Makefile +++ b/conditor-dumps/Makefile @@ -6,10 +6,10 @@ .DELETE_ON_ERROR: # To prevent deleting intermediate files (for controls) -.PRECIOUS: 02-download/%.jsonl 04-enrich/%.jsonl +.PRECIOUS: 02-download/%.jsonl 03-enrich/%.jsonl SOURCE_FILES := $(wildcard 01-query/*.txt) -TARGET_FILES := $(patsubst 01-query/%.txt, 05-report/%.log, $(SOURCE_FILES)) +TARGET_FILES := $(patsubst 01-query/%.txt, 04-report/%.log, $(SOURCE_FILES)) watch: ## Automatically build files when they change while true; do \ @@ -22,13 +22,13 @@ echo $(TARGET_FILES) # cible : dépendance -05-report/%.log: 04-enrich/%.jsonl +04-report/%.log: 03-enrich/%.jsonl mkdir -p $(@D) wc -l $< > $@ -04-enrich/%.jsonl: 02-download/%.jsonl +03-enrich/%.jsonl: 02-download/%.jsonl mkdir -p $(@D) - time npx ezs 04-enrich.ini < $< > $@.crdownload + time npx ezs 03-enrich.ini < $< > $@.crdownload mv $@.crdownload $@ 02-download/%.jsonl: 01-query/%.txt diff --git a/conditor-dumps/README.md b/conditor-dumps/README.md index e3f6e5f..04dd265 100644 --- a/conditor-dumps/README.md +++ b/conditor-dumps/README.md @@ -3,7 +3,7 @@ Collecte, restructuration et enrichissement de données [Conditor](https://corhal-api.inist.fr/api-docs/). -Les fichiers fournis sont au format JSON Lines (`.jsonl`), ce qui veut dire que le *loader* utilisé pour LODEX doit être adapté. +Les fichiers fournis sont au format JSON Lines (`.jsonl`) > 📗 Tant qu'une étape n'est pas terminée, le fichier résultant est suffixé par `.crdownload`. @@ -12,58 +12,70 @@ ### 01-query Dépôt d'un fichier `NOM_DU_FICHIER.txt` (requête) dans le répertoire `01-query`. +Pour effectuer des études sur les instituts CNRS et utiliser le loader approprié (qui filtre sur l'année et l'institut en question) veuillez nommer les champs de la façon suivante : `IN2P3_2017_juin.txt` soit l'institut, l'année interrogée et le mois où le dump est effectué. ### 02-download Téléchargement de données via la requête sur l'api Corhal. -Création des champs suivants : +Création/modification des champs suivants : -- `ApilPublicationDate` à partir de données existantes dans le json (`publicationDate` et `electronicPublicationDate`). -- `ApilFinancement` à partir du champ `funders` -- `ApilRnsr` au niveau `authors/affiliations`, à partir du champ - `authors/affiliations/rnsr` et s'il n'est pas présent à partir du champ - `authors/affiliations/enrichments/rnsr`. +- `abstract` à partir du champ `abstract.default` +- `title` à partir du champ `title.default` +- `halClassification` à partir du champ `classifications.enrichments.hal.fr` +- `keywordsAuthorsAndMesh` qui regroupe les mots clés en anglais des mots clés auteurs et Mesh. +- `conferenceDetails` qui concatène les données `name`, `date`, `place` et `country` du champ `host.conference` +- `volumeIssueAndPages` qui concatène les données `volume`, `issue` et `pages.range` du champ `host.conference` +- `issn`, `eissn`, `isbn` et `language` tous issus du champ `host` +- `publicationYear` à partir de données existantes dans le json (`publicationDate` et `electronicPublicationDate`). +- `isFunded` qui indique si le champ `funders` est vide ou non. +- `funders` à partir du champ `funders.fullname` +- `codesRnsr` qui récupère tous les rnsr présents à différents niveaux d'imbrication : `authors/affiliations/rnsr`, `authors/affiliations/enrichments/rnsr`et `authors.rnsr` + À noter: 4 RNSR sont systématiquement enlevés (200612821P, 200018571R, 199812965F, 201523784S). -> Note: l'étape 03 a été intégrée à l'étape 2. -> -> Note 2: on préserve le champ `sourceUidChain` à la racine de la notice. +- `sourceUidChain` à partir du champ `business.sourceUidChain` +- Modification du champ `enrichments.openAccess.unpaywall.oaLocations` qui, s'il est vide, est enrichi d'une clé `hostType` à laquelle est associée la valeur "OA - Inconnu". +- Le champ `authors` est dépouillé de toutes les propriétés n'étant pas utiles afin d'alléger sa taille. +- Enfin tous les champs n'étant pas ou plus utiles (car exploités) sont supprimés, toujours pour réduire la taille des fichiers. -### 04-enrich -Appel de web services ([Loterre - Structures de recherche](https://openapi.services.inist.fr/?urls.primaryName=loterre-resolvers%20-%20R%C3%A9solveurs%20pour%20des%20terminologies%20Loterre#/loterre-resolvers/post-v1-2XK-identify), [instituts CNRS](https://openapi.services.inist.fr/?urls.primaryName=mapping-tools%20-%20Utilisation%20de%20tables%20de%20correspondance#/mapping/post-v1-rnsr-year-instituts-cnrs)) pour créer les champs suivants : +### 03-enrich -- `ApilWsIsCnrs` -- `ApilWsLaboSigle` (indisponible depuis la version 1.13.0) -- `ApilWsLaboIntitule` -- `ApilRnsr` à la racine, à partir des champs `authors/affiliations/ApilRnsr` dédoublonnés. -- `ApilWsInstitutCnrs` à partir du champ `ApilRnsr` -- `ApilWsSigleLaboIntitule` (indisponible depuis la version 1.13.0) +Appel du web service [Hal](https://openapi.services.istex.fr/?urls.primaryName=biblio-tools%20-%20Outils%20pour%20r%C3%A9f%C3%A9rences%20bibliographiques#/biblio-tools/post-v2-hal-works-expand) afin de récupérer les RNSR manquants. Ceux-ci peuvent manquer pour 2 raisons : +- la notice n'est pas pertinente, elle a pu être récupérée par exemple car la recherche du terme "INEE" renvoie des notices avec le terme "engineering" dans les affiliations. +- les RNSR figurent dans la notice Hal, mais ils sont perdus lors de la création la notice unifiée car Hal n'est pas la source prioritaire. +Ainsi, on isole toutes les notices où le champ `codesRnsr` est vide. On remplace le tableau vide par le DOI, puis on lance le web service sur ces notices uniquement. Les résultats sont stockés dans un champ provisoire nommé `wsHal`. A l'aide d'un script on récupère tous les RNSR présents à différents niveaux de l'objet dans le champ `retrieveHalRnsr`. Enfin, on ajoute ces RNSR dans le champ `codesRnsr` et on supprime les 2 autres champs. -Appel de plusieurs web services de mapping ([documentType](https://openapi.services.inist.fr/?urls.primaryName=mapping-tools%20-%20Utilisation%20de%20tables%20de%20correspondance#/mapping/post-v1-homogenize-document-type-json), [source](https://openapi.services.inist.fr/?urls.primaryName=mapping-tools%20-%20Utilisation%20de%20tables%20de%20correspondance#/mapping/post-v1-homogenize-source-json), [publisher](https://openapi.services.inist.fr/?urls.primaryName=mapping-tools%20-%20Utilisation%20de%20tables%20de%20correspondance#/mapping/post-v1-homogenize-publisher-json)) et d'outils bibliographiques ([crossref](https://openapi.services.inist.fr/?urls.primaryName=biblio-tools%20-%20Outils%20pour%20r%C3%A9f%C3%A9rences%20bibliographiques#/biblio-tools/post-v1-crossref-prefixes-expand)) pour créer les champs suivants : +Appel du web service [Loterre - Structures de recherche](https://openapi.services.istex.fr/?urls.primaryName=loterre-resolvers%20-%20R%C3%A9solveurs%20pour%20des%20terminologies%20Loterre#/loterre-resolvers/post-v1-9SD-identify) pour créer les champs suivants : -- `ApilWsTypeDoc` homogénéisé à partir du champ `originalGenre`. Si - l'homogénéisation renvoie un "n/a" alors on récupère la valeur d'origine - `originalGenre`. -- `ApilWsSource` Récupération du champ `host/title`. Si vide, alors récupération - du champ `host/conference/name`. Homogénéisation des sources, si résultat - "n/a", conservation de la valeur d'origine (`host/title` ou - `host/conference/name`). -- `ApilWsPublisher` champ d'origine `host.publisher` et par défaut on récupère - un éditeur à partir de la racine du champ `doi`. Si le champ `doi` renvoie un "n/a" alors on récupère le champ d'origine `host.publisher`. +- `laboIntitule` +- `delegationsRegionales` (les codes des DR sont verbalisés selon un dictionnaire défini dans l'instruction [ENV]. Les anciens codes ont été ajoutés et rattachés à leur délégation actuelle.) +- `isCnrs` à partir du champ `institut`. Renvoie "Non" si le champs est vide, "Oui" s'il ne l'est pas. + +Appel des web services ([OpenAlex](https://openapi.services.istex.fr/?urls.primaryName=biblio-tools%20-%20Outils%20pour%20r%C3%A9f%C3%A9rences%20bibliographiques#/biblio-tools/post-v1-openalex-works-expand), [Crossref](https://openapi.services.istex.fr/?urls.primaryName=biblio-tools%20-%20Outils%20pour%20r%C3%A9f%C3%A9rences%20bibliographiques#/biblio-tools/post-v1-crossref-prefixes-expand)). Ces web services prenant des DOI en entrée, des instructions ont été ajoutées afin de ne pas traiter les documents qui n'en possèdent pas. Cela évite d'envoyer des milliers de données qui ne renverront de toute façon pas de réponse. + +- l'appel d'OpenAlex permet de créer le champ `isRetracted` et de récupérer les `oa status` "diamond" qui enrichiront plusieurs champs portant sur l'Open Access. +- l'appel de Crossref permet de récupérer des données sur les éditeurs à partir de la racine des DOI. Ces données sont utilisées plus tard pour créer le champ `publisher` + +Les web services de mapping étant voués à disparaître, plusieurs enrichissements se font désormais via des tables de correspondance hébergées sur un serveur EZmaster. Les traitements sont réalisés par des sous-flux dédiés et définis dans des fichiers externes pour créer les champs suivants : + +- `institutCnrs` généré à partir du fichier `03.1-enrich-rnsrByYear.ini`. Le champ est ensuite divisé en deux champs distincts : `institutsPrincipaux` et `institutsSecondaires` +- `documentType` généré à partir du fichier `03.2-enrich-docType.ini`. Homogénéisé à partir du champ originalGenre. Si l'homogénéisation renvoie un "n/a" alors on récupère la valeur d'origine. +- `source` généré à partir du fichier `03.3-enrich-source.ini`. Récupération du champ `host/title`. Si vide, alors récupération du champ `host/conference/name`. Homogénéisation des sources, si résultat "n/a", conservation de la valeur d'origine (`host/title` ou `host/conference/name`). +- `publisher` généré à partir du fichier `03.4-enrich-publisher.ini`. Champ d'origine `host.publisher` et par défaut on récupère un éditeur à partir de la racine du champ `doi`. Si le champ `doi` renvoie un "n/a" alors on récupère le champ d'origine `host.publisher`. Appel de web services ([adresses](https://openapi.services.inist.fr/?urls.primaryName=affiliations-tools%20-%20Structuration%20%26%20enrichissements%20d%27affiliations#/adresses/post-v1-addresses-parse), [Loterre - Pays](https://openapi.services.inist.fr/?urls.primaryName=loterre-resolvers%20-%20R%C3%A9solveurs%20pour%20des%20terminologies%20Loterre#/loterre-resolvers/post-v1-9SD-identify)) pour créer les champs suivants : - `ApilWSCodeISO` récupéré à partir du champ `Authors.affiliation.address`. - `ApilWSCountry` récupéré à partir du champ `Authors.affiliation.address`. -### 05-report +### 04-report Cette étape sert à générer un fichier par requête, contenant le nombre de lignes du fichier (et donc le nombre de notices) et le nom du fichier correspondant -dans le répertoire `04-enrich`. +dans le répertoire `03-enrich`. ## Configuration