diff --git a/data-workflow/22-txt-en.tar.gz b/data-workflow/22-txt-en.tar.gz new file mode 100644 index 0000000..5a8b914 --- /dev/null +++ b/data-workflow/22-txt-en.tar.gz Binary files differ diff --git a/data-workflow/README.md b/data-workflow/README.md new file mode 100644 index 0000000..a250cc2 --- /dev/null +++ b/data-workflow/README.md @@ -0,0 +1,9 @@ +# data-workflow + +Enchaînement asynchrone de traitements + +Les worflows permettent de traiter des fichiers corpus compressés en appelant +des webservices d'enrichissement par document de manière asynchrone. + +> **Attention**: le code source de ce service a été migré sur . + diff --git a/data-workflow/examples.http b/data-workflow/examples.http index 985ab59..a4e396a 100644 --- a/data-workflow/examples.http +++ b/data-workflow/examples.http @@ -1,11 +1,14 @@ -# File Global Variables: Variables defined in Region without name or request -@baseUrl = http://localhost:31976 -#@baseUrl = https://data-workflow.services.istex.fr/ +# These examples can be used directly in VSCode, using HTTPYac extension (anweber.vscode-httpyac) +# They are important, because used to generate the tests.hurl file. + +# Décommenter/commenter les lignes voulues pour tester localement +@host=http://localhost:31976 +# @host=https://data-workflow.services.istex.fr ### # @name v1Retrieve # @save -POST {{baseUrl}}/v1/retrieve HTTP/1.1 +POST {{host}}/v1/retrieve HTTP/1.1 Content-Type: application/json [ @@ -16,7 +19,7 @@ ### # @name v1collect -POST {{baseUrl}}/v1/collect?indent=true HTTP/1.1 +POST {{host}}/v1/collect?indent=true HTTP/1.1 Content-Type: application/json [ @@ -27,20 +30,33 @@ ### # @name v1baseLine -POST {{baseUrl}}/v1/base-line HTTP/1.1 +POST {{host}}/v1/base-line HTTP/1.1 Content-Type: application/x-tar X-Webhook-Success: https://webhook.site/69300b22-a251-4c16-9905-f7ba218ae7e9 X-Webhook-Failure: https://webhook.site/69300b22-a251-4c16-9905-f7ba218ae7e9 -< ./example-json.tar.gz +< ./22-txt-en.tar.gz ### -## @name v1tagCloudFr -POST {{baseUrl}}/v1/tag-cloud-fr HTTP/1.1 -Content-Type: application/x-tar +## @name v1tagCloudEn +POST {{host}}/v1/tag-cloud-en HTTP/1.1 +Content-Type: application/x-gzip X-Webhook-Success: https://webhook.site/46d41346-6919-410f-a635-ae78146c2782 X-Webhook-Failure: https://webhook.site/46d41346-6919-410f-a635-ae78146c2782 -< ./abstract-fr.tar.gz +< ./22-txt-en.tar.gz + + +### +# @name v1RetrieveJson +# @save +POST {{host}}/v1/retrieve-json HTTP/1.1 +Content-Type: application/json + +[ + { + "value":"9tWhLX8Mr" + } +] ### diff --git a/data-workflow/swagger.json b/data-workflow/swagger.json index a2c0f43..226b4db 100644 --- a/data-workflow/swagger.json +++ b/data-workflow/swagger.json @@ -1,33 +1,33 @@ { - "openapi": "3.1.0", - "info": { - "title": "data-workflow - Enchainement de traitements asynchrones", - "summary": "Les worflows permettent de traiter des fichiers corpus compressés en appelant des webservices d'enrichissement par documents (webservices synchrones)", - "version": "1.2.7", - "termsOfService": "https://services.istex.fr/", - "contact": { - "name": "Inist-CNRS", - "url": "https://www.inist.fr/nous-contacter/" - } - }, - "servers": [ - { - "x-comment": "Will be automatically completed by the ezs server." - }, - { - "url": "http://vptdmjobs.intra.inist.fr:49158/", - "description": "production release", - "x-profil": "Standard" - } - ], + "openapi": "3.0.0", + "info": { + "title": "data-workflow - Enchaînement asynchrone de traitements", + "description": "Les worflows permettent de traiter des fichiers corpus compressés en appelant des webservices d'enrichissement par document de manière asynchrone.", + "version": "1.2.8", + "termsOfService": "https://services.istex.fr/", + "contact": { + "name": "Inist-CNRS", + "url": "https://www.inist.fr/nous-contacter/" + } + }, + "servers": [ + { + "x-comment": "Will be automatically completed by the ezs server." + }, + { + "url": "http://vptdmjobs.intra.inist.fr:49176/", + "description": "Latest version for production", + "x-profil": "Standard" + } + ], "tags": [ { "name": "data-workflow", - "description": "Conversions en fichier corpus compressé", + "description": "Enchaînement asynchrone de traitements", "externalDocs": { "description": "Plus de documentation", - "url": "https://gitbucket.inist.fr/tdm/web-services/tree/master/data-workflow" + "url": "https://github.com/inist-cnrs/web-services/tree/main/services/data-workflow" } } ] -} +} \ No newline at end of file diff --git a/data-workflow/tests.hurl b/data-workflow/tests.hurl new file mode 100644 index 0000000..38cad10 --- /dev/null +++ b/data-workflow/tests.hurl @@ -0,0 +1,105 @@ +# WARNING: This file was not generated, but manually written. +# DON'T OVERWRITE IT +# Use it to test: +# npx hurl --test --variable host="http://localhost:31976" tests.hurl +# or (from root of the repo) +# npm run test:local data-termsuite + +# WARNING: The webhook URLs don't last forever. +# Without activity, they expire after one week! +# That can be the cause of failing tests, using docker. +# Thus, there are commented here. + +############################################################################ +# Test v1/base-line + +POST {{host}}/v1/base-line +content-type: application/x-gzip +# X-Webhook-Success: https://webhook.site/684dd427-5484-404a-b5a0-80e5b6726a1c +# X-Webhook-Failure: https://webhook.site/684dd427-5484-404a-b5a0-80e5b6726a1c +file,./22-txt-en.tar.gz; + +HTTP 200 +Content-Type: application/json +# Capture the computing token +[Captures] +computing_token: jsonpath "$[0].value" +[Asserts] +variable "computing_token" exists + +# There should be a waiting time, representing the time taken to process data. +# Fortunately, as the data is sparse, and the computing time is small, +# the need is small. +# In normal use cases, a webhook is called when the processing is finished. +# That query gives the process identifier. +# Next, you can call the retrieve URL by putting the identifier in the value field. + +# Version 4.1.0 of hurl added a delay option, which value is milliseconds. +# https://hurl.dev/blog/2023/09/24/announcing-hurl-4.1.0.html#add-delay-between-requests + +POST {{host}}/v1/retrieve-json +content-type: application/json +Connection: Keep-Alive +Keep-Alive: 300 +Accept-Encoding: gzip, deflate +[Options] +delay: 1000 +``` +[ + { + "value":"{{computing_token}}" + } +] +``` + +HTTP 200 +Content-Type: application/json +[Asserts] +jsonpath "$" count == 22 +jsonpath "$[0].value" startsWith " Paleogene evolution and demise" + +# From here, the tests can't succeed on GitHub Actions, please test using +# real-tests.hurl + +# ############################################################################ +# # Test v1/tag-cloud-en + +# POST {{host}}/v1/tag-cloud-en +# content-type: application/x-gzip +# # X-Webhook-Success: https://webhook.site/684dd427-5484-404a-b5a0-80e5b6726a1c +# # X-Webhook-Failure: https://webhook.site/684dd427-5484-404a-b5a0-80e5b6726a1c +# file,./22-txt-en.tar.gz; + +# HTTP 200 +# Content-Type: application/json +# # Capture the computing token +# [Captures] +# computing_token: jsonpath "$[0].value" +# [Asserts] +# variable "computing_token" exists + +# # There should be a waiting time, representing the time taken to process data. +# # Fortunately, as the data is sparse, and the computing time is small, +# # the need is small. +# # In normal use cases, a webhook is called when the processing is finished. +# # That query gives the process identifier. +# # Next, you can call the retrieve URL by putting the identifier in the value field. + +# # Version 4.1.0 of hurl added a delay option, which value is milliseconds. +# # https://hurl.dev/blog/2023/09/24/announcing-hurl-4.1.0.html#add-delay-between-requests + +# POST {{host}}/v1/retrieve-json +# content-type: application/json +# [Options] +# delay: 2000 +# ``` +# [ +# { +# "value":"{{computing_token}}" +# } +# ] +# ``` + +# HTTP 200 +# Content-Type: application/json +# [{"id":"aridification","value":2},{"id":"mafic","value":2},{"id":"climate change","value":3}] diff --git a/data-workflow/v1/base-line.ini b/data-workflow/v1/base-line.ini deleted file mode 100644 index 6e806a4..0000000 --- a/data-workflow/v1/base-line.ini +++ /dev/null @@ -1,59 +0,0 @@ -# Entrypoint output format -mimeType = application/json - -# OpenAPI Documentation - JSON format (dot notation) -post.operationId = post-v1-base-line -post.summary = Chargement et analyse d'un fichier corpus -post.description = Le corpus est analysé et restitué sans modification des données -post.tags.0 = data-workflow -post.requestBody.content.application/x-gzip.schema.type = string -post.requestBody.content.application/x-gzip.schema.format = binary -post.requestBody.content.application/x-tar.schema.type = string -post.requestBody.content.application/x-tar.schema.format = binary -post.requestBody.required = true -post.responses.default.description = Informations permettant de récupérer les données le moment venu -post.parameters.0.description = Indenter le JSON résultant -post.parameters.0.in = query -post.parameters.0.name = indent -post.parameters.0.schema.type = boolean -post.parameters.1.description = URL pour signaler que le traitement est terminé -post.parameters.1.in = header -post.parameters.1.name = X-Webhook-Success -post.parameters.1.schema.type = string -post.parameters.1.schema.format = uri -post.parameters.1.required = false -post.parameters.2.description = URL pour signaler que le traitement a échoué -post.parameters.2.in = header -post.parameters.2.name = X-Webhook-Failure -post.parameters.2.schema.type = string -post.parameters.2.schema.format = uri -post.parameters.2.required = false - -[env] -path = generator -value = base-line - -# Step 1 (générique): Charger le fichier corpus -[delegate] -file = charger.cfg - -# Step 2 (générique): Traiter de manière asynchnore les items reçus -[fork] -standalone = true -logger = logger.cfg - -# Step 2.0 (optionnel): Accélére le détachement du fork si l'enrichissement est lent -[fork/delegate] -file = buffer.cfg - -# Step 2.1 (spécifique): Lancer un calcul sur tous les items reçus -[fork/transit] - -# Step 2.2 (générique): Enregister le résulat et signaler que le traitment est fini -[fork/delegate] -file = recorder.cfg - -# Step 3 : Renvoyer immédiatement un seul élément indiquant comment récupérer le résulat quand il sera prêt -[delegate] -file = recipient.cfg - diff --git a/data-workflow/v1/buffer.cfg b/data-workflow/v1/buffer.cfg deleted file mode 100644 index b291af8..0000000 --- a/data-workflow/v1/buffer.cfg +++ /dev/null @@ -1,29 +0,0 @@ -[use] -plugin = basics - -# On sauvegarde sur disque pour accepter rapidement tous les objets en entrée -# et répondre rapidement au client que le traitmenent asynchnrone est lancé. -# -# Le "fork" se détache uniquement quand tous les objets sont "rentrés" dans le fork -# Si le traitement est plus lent que la sauvegarde sur disque -# il est nécessaire de créer un fichier temporaire -[pack] -[FILESave] -identifier = env('identifier') -location = /tmp/upload -compress = true - -[debug] -text = fix('Data received by', env('generator'), 'for', env('identifier')).join(' ') - -[exchange] -value = get('filename') - -[FILELoad] -compress = true -location = /tmp/upload -[unpack] - -[metrics] -bucket = buffer - diff --git a/data-workflow/v1/charger.cfg b/data-workflow/v1/charger.cfg deleted file mode 100644 index 228af1d..0000000 --- a/data-workflow/v1/charger.cfg +++ /dev/null @@ -1,30 +0,0 @@ -[use] -plugin = basics - -# Step 0 (générique) : Lire le fichier standard tar.gz -[TARExtract] -compress = true -path = */*.json - -# Step 1 (générique) : Créer un identifiant unique pour le corpus reçu -[singleton] - -# Step 1.1 : On évite de récupere un champ uri existant -[singleton/env] -path = pid -value = fix(`PID${Date.now()}`) - -# Step 1.2 : On génére un identifiant unique -[singleton/identify] -path = env('pid') - -# Step 1.3: On garde en mémoire l'identifiant généré (en le simplifiant) -[singleton/env] -path = identifier -value = get(env('pid')).replace('uid:/', '') - -[singleton/exchange] -value = self().omit([env('pid')]) - -[metrics] -bucket = charger diff --git a/data-workflow/v1/conditormetrie.cfg b/data-workflow/v1/conditormetrie.cfg deleted file mode 100644 index c89ac5b..0000000 --- a/data-workflow/v1/conditormetrie.cfg +++ /dev/null @@ -1,394 +0,0 @@ -[use] -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"}) - -[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)) - -[assign] -path = ApilFinancement -value = get('funders').castArray().filter(Boolean).thru(arr => Boolean(arr.length)) - -# 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 -[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 -[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 -value= get("allApilRnsr1").concat(self.ApilRnsr2).compact().uniq() - -# Garde un identifiant -[assign] -path = sourceUidChain -value = get("business.sourceUidChain") - -#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 -[exchange] -value = omit(['business','origins','technical','allApilRnsr1','ApilRnsr2']) -append = pack - - -# Quand les RNSR ne sont pas fournis dans authors.*.affiliations.*.rnsr -# on utilise le Web Service qui les met au même niveau dans wsRnsr - -# 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 })) - -[expand] -path = ws.loterre2xk - -[expand/exploding] - -[expand/expand] -path = value.itemApilRnsr -size = 100 -cacheName = 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 -cacheName = 04-rnsr-year-instituts-cnrs - -[expand/expand/URLConnect] -url = https://mapping-tools.services.istex.fr/v1/rnsr-year/instituts-cnrs -timeout = 90002 -noerror = true - -[expand/assign] -path = value.label -value = get('value.itemApilRnsr.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()) - -path = value.dr -value = get("value.itemApilRnsr.delegationRegionale_dep").castArray().compact().map(n => _.get(env("number2labelDR"), n, `unknow ${n}` )) - -[expand/aggregate] - -[assign] -path = ApilRnsr -value = get("ws.loterre2xk").castArray().map('codeRNSR') - -path = ApilWsLaboIntitule -value = get("ws.loterre2xk").castArray().map('label') - -path = ApilWsDr -value = get("ws.loterre2xk").castArray().map('dr') - -path = ApilWsInstitutCnrs -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 -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 -[assign] -path=ApilWsOpenalex -value = get("doi") - -[expand] -path = ApilWsOpenalex -size = 100 - -[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 && String(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") - -##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") - -#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") - -#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' -[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() - -#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")) - -[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")) - -#On détermine l'ordre de provenance des notices composants la notice Conditor -[assign] -path = ApilProvenance -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) - -[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() - -#Homogénéise les types de document -[assign] -path = ApilWsTypeDoc -value = get("originalGenre").trim() - -[expand] -path = ApilWsTypeDoc -size = 100 -cacheName = 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") - -# On l'écrase avec la valeur de "originalGenre" -[swing/assign] -path = ApilWsTypeDoc -value = get("originalGenre").trim() - -# Homogénéise les sources -[assign] -path = ApilWsSource -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 -size = 100 -cacheName = 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") - -[swing/assign] -path = ApilWsSource -value = get("host.title",_.get(self,"host.conference.name")).trim() - -# Traitement des éditeurs -[assign] -path = ws.ApilRacineDoiPublisher -value = get("doi").split('/').filter(i => i.match(/^10./)).pop() - -[expand] -path = ws.ApilRacineDoiPublisher -size = 1 -cacheName = 04-api-crossref-prefixes-expand - -[expand/URLFetch] -target = value -url = fix('https://api.crossref.org/prefixes/').append(self.value) -json = true -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 -value = get("host.publisher",_.get(self,"ws.ApilRacineDoiPublisher")) - -# Homogénéise l'éditeur -[assign] -path = ApilWsPublisher -value = get("tmp.ApilWsPublisher").trim() - -[expand] -path = ApilWsPublisher -size = 100 -cacheName = 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, -[swing] -test = has("host.publisher") -test = get("ApilWsPublisher").isEqual("n/a") - -# On l'écrase avec la valeur de host.publisher -[swing/assign] -path = ApilWsPublisher -value = get("host.publisher") - -# 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 = 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 = 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 -value = get("ws.libpostal").castArray().filter(Boolean) \ - .map(n => n.country?.cartographyCode) \ - .uniq().filter(Boolean) - -path = ApilWsCountry -value = get("ws.libpostal").castArray().filter(Boolean) \ - .map(n => n.country?.["prefLabel@en"]) \ - .uniq().filter(Boolean) - -# Suppression des champs non voulus -[exchange] -value = omit(['tmp']) diff --git a/data-workflow/v1/conditormetrie.ini b/data-workflow/v1/conditormetrie.ini deleted file mode 100644 index 477427c..0000000 --- a/data-workflow/v1/conditormetrie.ini +++ /dev/null @@ -1,60 +0,0 @@ -# Entrypoint output format -mimeType = application/json - -# OpenAPI Documentation - JSON format (dot notation) -post.operationId = post-v1-conditormetrie -post.summary = Enrichissements bibliométriques sur un corpus Conditor -post.description = Le résultat produit une liste de notices enrichies -post.tags.0 = data-workflow -post.requestBody.content.application/x-gzip.schema.type = string -post.requestBody.content.application/x-gzip.schema.format = binary -post.requestBody.content.application/x-tar.schema.type = string -post.requestBody.content.application/x-tar.schema.format = binary -post.requestBody.required = true -post.responses.default.description = Informations permettant de récupérer les données le moment venu -post.parameters.0.description = Indenter le JSON résultant -post.parameters.0.in = query -post.parameters.0.name = indent -post.parameters.0.schema.type = boolean -post.parameters.1.description = URL pour signaler que le traitement est terminé -post.parameters.1.in = header -post.parameters.1.name = X-Webhook-Success -post.parameters.1.schema.type = string -post.parameters.1.schema.format = uri -post.parameters.1.required = false -post.parameters.2.description = URL pour signaler que le traitement a échoué -post.parameters.2.in = header -post.parameters.2.name = X-Webhook-Failure -post.parameters.2.schema.type = string -post.parameters.2.schema.format = uri -post.parameters.2.required = false - -[env] -path = generator -value = conditormetrie - -# Step 1 (générique): Charger le fichier corpus -[delegate] -file = charger.cfg - -# Step 2 (générique): Traiter de manière asynchnore les items reçus -[fork] -standalone = true -logger = logger.cfg - -# Step 2.0 (optionnel): Accélére le détachement du fork si l'enrichissement est lent -[fork/delegate] -file = buffer.cfg - -# Step 2.1 (spécifique): Lancer un calcul sur tous les items reçus -[fork/delegate] -file = conditormetrie.cfg - -# Step 2.2 (générique): Enregister le résulat et signaler que le traitment est fini -[fork/delegate] -file = recorder.cfg - -# Step 3 : Renvoyer immédiatement un seul élément indiquant comment récupérer le résulat quand il sera prêt -[delegate] -file = recipient.cfg - diff --git a/data-workflow/v1/logger.cfg b/data-workflow/v1/logger.cfg deleted file mode 100644 index 6e44e2d..0000000 --- a/data-workflow/v1/logger.cfg +++ /dev/null @@ -1,55 +0,0 @@ -; [use] -plugin = basics -plugin = analytics - -[metrics] -bucket = logger - -# On ne garde que la première erreur déclénchée -[shift] - -[debug] -text = Error trapped - -[assign] -path = body.identifier -value = env('identifier') - -path = body.generator -value = env('generator') - -path = body.error.type -value = get('type') - -path = body.error.scope -value = get('scope') - -path = body.error.message -value = get('message') - -path = env -value = env() - -[swing] -test = env('headers.x-webhook-failure').startsWith('http') - -[swing/URLFetch] -url = env('headers.x-webhook-failure').trim() -path = body -headers = Content-Type:application/json -target = result -retries = 5 -timeout = 30000 - -# On enregistre uniqument quelques informations (à supprimer pour avoir la trace complète) -[exchange] -value = get('body') - -[FILESave] -location = /tmp/retrieve -identifier = env('identifier') -jsonl = true -compress = false - -[debug] -text = Error was saved diff --git a/data-workflow/v1/recipient.cfg b/data-workflow/v1/recipient.cfg deleted file mode 100644 index f723830..0000000 --- a/data-workflow/v1/recipient.cfg +++ /dev/null @@ -1,12 +0,0 @@ -[use] -plugin = basics - -[shift] -[replace] -path = id -value = env('generator') -path = value -value = env('identifier') - -[JSONString] -indent = env('indent') diff --git a/data-workflow/v1/recorder.cfg b/data-workflow/v1/recorder.cfg deleted file mode 100644 index d7a789c..0000000 --- a/data-workflow/v1/recorder.cfg +++ /dev/null @@ -1,54 +0,0 @@ -[use] -plugin = basics -plugin = analytics - -[singleton] -[singleton/debug] -text = fix('One first result received by', env('generator'), 'for', env('identifier')).join(' ') -[metrics] -bucket = recorder - -# Step 2.2 (générique): Création d'un fichier résulat standard -[TARDump] -compress = true -manifest = fix({version: '1'}) -manifest = fix({identifier: env('identifier')}) -manifest = fix({generator: env('generator')}) - -# Step 2.3 (générique): Sauvegarder sur disque le résulat -[FILESave] -location = /tmp/retrieve -identifier = env('identifier') -jsonl = false -compress = false - -# Step 2.4 (générique): Signaler le fin du traitement via un appel à un webhook (si il a été précisé) -[swing] -test = env('headers.x-webhook-success').startsWith('http') - -# Step 2.4.1 (générique): Séléctionner les informations à envoyer au webhook -[swing/replace] -path = url -value = env('headers.x-webhook-success') -path = body -value = self().pick(['size', 'atime', 'mtime', 'ctime']).set('identifier', env('identifier')).set('generator', env('generator')).set('state', 'ready') - -[swing/debug] -text = fix('Result generated by', env('generator'), 'for', env('identifier')).join(' ') -# Step 2.4.2 (générique): Envoyer la requète HTTP -[swing/URLFetch] -url = env('headers.x-webhook-success').trim() -path = body -headers = Content-Type:application/json -retries = 5 -timeout = 30000 - -# Step 2.4.3 (faculatif) : Ajouter une trace dans log -[swing/debug] -text = fix('WebHook triggered by', env('generator'), 'for', env('identifier')).join(' ') - -# Step 2.5 (faculatif) : Ajouter une trace dans log -[debug] -text = fix('Process completed by', env('generator'), 'for', env('identifier')).join(' ') - - diff --git a/data-workflow/v1/retrieve-csv.ini b/data-workflow/v1/retrieve-csv.ini deleted file mode 100644 index 2d4cd4c..0000000 --- a/data-workflow/v1/retrieve-csv.ini +++ /dev/null @@ -1,35 +0,0 @@ -# Entrypoint output format -mimeType = text/csv - -# OpenAPI Documentation - JSON format (dot notation) -post.operationId = post-v1-retrieve-csv -post.description = Récupération d'un résultat produit sous forme d'un flux CSV -post.summary = Les traitements étant asynchrones le résultat, une fois créé, doit être récupéré par cette route -post.tags.0 = data-computer -post.responses.default.description = Fichier corpus en version CSV -post.requestBody.content.application/json.example.0.value = xMkWJX7GU -post.requestBody.content.application/json.schema.$ref = #/components/schemas/JSONStream -post.requestBody.required = true - -[use] -plugin = basics - -[JSONParse] -separator = * - -[exchange] -value = get('value') - -[FILELoad] -location = /tmp/retrieve - -[TARExtract] -compress = true -path = */*.json - -[exchange] -value = self().mapValues(value => typeof value === 'object' ? JSON.stringify(value) : value) - -[CSVString] -separator = fix(',') -format = strict diff --git a/data-workflow/v1/retrieve-json.ini b/data-workflow/v1/retrieve-json.ini deleted file mode 100644 index ed41949..0000000 --- a/data-workflow/v1/retrieve-json.ini +++ /dev/null @@ -1,36 +0,0 @@ -# Entrypoint output format -mimeType = application/json - -# OpenAPI Documentation - JSON format (dot notation) -post.operationId = post-v1-retrieve-json -post.summary = Récupération d'un résultat produit sous forme d'un flux JSON -post.description = Les traitements étant asynchrones le résultat une fois créé doit être récupéré par cette route -post.tags.0 = data-computer -post.responses.default.description = Fichier corpus au format JSON -post.requestBody.content.application/json.example.0.value = xMkWJX7GU -post.requestBody.content.application/json.schema.$ref = #/components/schemas/JSONStream -post.requestBody.required = true -post.parameters.0.description = Indenter le JSON résultant -post.parameters.0.in = query -post.parameters.0.name = indent -post.parameters.0.schema.type = boolean - -[use] -plugin = basics - -[JSONParse] -separator = * - -[exchange] -value = get('value') - -[FILELoad] -location = /tmp/retrieve - -[TARExtract] -compress = true -path = */*.json - -[JSONString] -indent = env('indent') - diff --git a/data-workflow/v1/retrieve.ini b/data-workflow/v1/retrieve.ini deleted file mode 100644 index 56a26c3..0000000 --- a/data-workflow/v1/retrieve.ini +++ /dev/null @@ -1,28 +0,0 @@ -# Entrypoint output format -mimeType = application/x-gzip -extension = tar.gz - -# OpenAPI Documentation - JSON format (dot notation) -post.operationId = post-v1-retrieve -post.summary = Récupération d'un résultat produit sous forme d'un fichier corpus -post.description = Les traitements étant asynchrones le résultat une fois créé doit être récupéré par cette route -post.tags.0 = data-computer -post.responses.default.description = Fichier corpus au format tar.gz -post.responses.default.content.application/x-gzip.schema.type = string -post.responses.default.content.application/x-gzip.schema.format = binary -post.requestBody.content.application/json.example.0.value = xMkWJX7GU -post.requestBody.content.application/json.schema.$ref = #/components/schemas/JSONStream -post.requestBody.required = true - -[use] -plugin = basics - -[JSONParse] -separator = * - -[exchange] -value = get('value') - -[FILELoad] -location = /tmp/retrieve - diff --git a/data-workflow/v1/tag-cloud-en.ini b/data-workflow/v1/tag-cloud-en.ini deleted file mode 100644 index 179fa48..0000000 --- a/data-workflow/v1/tag-cloud-en.ini +++ /dev/null @@ -1,63 +0,0 @@ -# Entrypoint output format -mimeType = application/json - -# OpenAPI Documentation - JSON format (dot notation) -post.operationId = post-v1-tag-cloud-en -post.summary = Le résultat produit une liste de termes associés à leurs fréquences -post.description = Chargement et analyse d'un fichier corpus pour compter le nombre de termes *anglais* pertinents identiques dans chaque document -post.tags.0 = data-workflow -post.requestBody.content.application/x-gzip.schema.type = string -post.requestBody.content.application/x-gzip.schema.format = binary -post.requestBody.content.application/x-tar.schema.type = string -post.requestBody.content.application/x-tar.schema.format = binary -post.requestBody.required = true -post.responses.default.description = Informations permettant de récupérer les données le moment venu -post.parameters.0.description = Indenter le JSON résultant -post.parameters.0.in = query -post.parameters.0.name = indent -post.parameters.0.schema.type = boolean -post.parameters.1.description = URL pour signaler que le traitement est terminé -post.parameters.1.in = header -post.parameters.1.name = X-Webhook-Success -post.parameters.1.schema.type = string -post.parameters.1.schema.format = uri -post.parameters.1.required = false -post.parameters.2.description = URL pour signaler que le traitement a échoué -post.parameters.2.in = header -post.parameters.2.name = X-Webhook-Failure -post.parameters.2.schema.type = string -post.parameters.2.schema.format = uri -post.parameters.2.required = false - -[env] -path = generator -value = tag-cloud-en - -path = language -value = en - -[use] -plugin = basics -plugin = analytics - -# Step 1 (générique): Charger le fichier corpus -[delegate] -file = charger.cfg - -# Step 2 (générique): Traiter de manière asynchnore les items reçus -[fork] -standalone = true -logger = logger.cfg - -# Step 2.1 (spécifique): Lancer un calcul sur tous les items reçus -[fork/delegate] -file = tag-cloud.cfg - -# Step 2.2 (générique): Enregister le résulat et signaler que le traitment est fini -[fork/delegate] -file = recorder.cfg - -# Step 3 : Renvoyer immédiatement un seul élément indiquant comment récupérer le résulat quand il sera prêt -[delegate] -file = recipient.cfg - diff --git a/data-workflow/v1/tag-cloud-fr.ini b/data-workflow/v1/tag-cloud-fr.ini deleted file mode 100644 index 9aa6e87..0000000 --- a/data-workflow/v1/tag-cloud-fr.ini +++ /dev/null @@ -1,63 +0,0 @@ -# Entrypoint output format -mimeType = application/json - -# OpenAPI Documentation - JSON format (dot notation) -post.operationId = post-v1-tag-cloud-fr -post.summary = Le résultat produit une liste de termes associés à leurs fréquences -post.description = Chargement et analyse d'un fichier corpus pour compter le nombre de termes *français* pertinents identiques dans chaque document -post.tags.0 = data-workflow -post.requestBody.content.application/x-gzip.schema.type = string -post.requestBody.content.application/x-gzip.schema.format = binary -post.requestBody.content.application/x-tar.schema.type = string -post.requestBody.content.application/x-tar.schema.format = binary -post.requestBody.required = true -post.responses.default.description = Informations permettant de récupérer les données le moment venu -post.parameters.0.description = Indenter le JSON résultant -post.parameters.0.in = query -post.parameters.0.name = indent -post.parameters.0.schema.type = boolean -post.parameters.1.description = URL pour signaler que le traitement est terminé -post.parameters.1.in = header -post.parameters.1.name = X-Webhook-Success -post.parameters.1.schema.type = string -post.parameters.1.schema.format = uri -post.parameters.1.required = false -post.parameters.2.description = URL pour signaler que le traitement a échoué -post.parameters.2.in = header -post.parameters.2.name = X-Webhook-Failure -post.parameters.2.schema.type = string -post.parameters.2.schema.format = uri -post.parameters.2.required = false - -[env] -path = generator -value = tag-cloud-fr - -path = language -value = fr - -[use] -plugin = basics -plugin = analytics - -# Step 1 (générique): Charger le fichier corpus -[delegate] -file = charger.cfg - -# Step 2 (générique): Traiter de manière asynchnore les items reçus -[fork] -standalone = true -logger = logger.cfg - -# Step 2.1 (spécifique): Lancer un calcul sur tous les items reçus -[fork/delegate] -file = tag-cloud.cfg - -# Step 2.2 (générique): Enregister le résulat et signaler que le traitment est fini -[fork/delegate] -file = recorder.cfg - -# Step 3 : Renvoyer immédiatement un seul élément indiquant comment récupérer le résulat quand il sera prêt -[delegate] -file = recipient.cfg - diff --git a/data-workflow/v1/tag-cloud.cfg b/data-workflow/v1/tag-cloud.cfg deleted file mode 100644 index 96e72cc..0000000 --- a/data-workflow/v1/tag-cloud.cfg +++ /dev/null @@ -1,30 +0,0 @@ -[use] -plugin = analytics - -[expand] -path = value -size = 100 -[expand/URLConnect] -url = fix(`https://terms-extraction.services.istex.fr/v1/teeft/${env('language', 'en')}?nb=10`) - -[exploding] -[replace] -path = id -value = get('value') - -path = value -value = 1 - -[groupingByEquality] - -[summing] - -[greater] -than = 1 -strict = true - -[replace] -path = id -value = get('id.0') -path = value -value = get('value')