182 lines
No EOL
6.3 KiB
Python
182 lines
No EOL
6.3 KiB
Python
import json as js
|
|
import re
|
|
import requests as req
|
|
from typing import Union
|
|
|
|
|
|
#region Funzione che processa una lista di CAS presa da Cosing (Grazie Jem)
|
|
|
|
def parse_cas_numbers(cas_string:list) -> list:
|
|
|
|
# Siccome ci assicuriamo esternamente che esista almeno un cas possiamo prendere la stringa
|
|
cas_string = cas_string[0]
|
|
|
|
# Rimuoviamo parentesi e il loro contenuto
|
|
cas_string = re.sub(r"\([^)]*\)", "", cas_string)
|
|
|
|
# Eseguiamo uno split su vari possibili separatori
|
|
cas_parts = re.split(r"[/;,]", cas_string)
|
|
|
|
# Otteniamo una lista utilizzando i cas precedenti e rimuovendo spazi in eccesso
|
|
cas_list = [cas.strip() for cas in cas_parts]
|
|
|
|
# Alcuni cas sono divisi da un doppio dash (--) che dobbiamo rimuovere
|
|
# è però necessario farlo ora in seconda battuta
|
|
|
|
if len(cas_list) == 1 and "--" in cas_list[0]:
|
|
|
|
cas_list = [cas.strip() for cas in cas_list[0].split("--")]
|
|
|
|
# Siccome alcuni cas hanno valori non validi ("-") li cerchiamo e rimuoviamo
|
|
if cas_list:
|
|
|
|
while "-" in cas_list:
|
|
cas_list.remove("-")
|
|
|
|
return cas_list
|
|
#endregion
|
|
|
|
#region Funzione per eseguire una ricerca direttamente sul cosing
|
|
|
|
# Il primo argomento è la stringa da cercare, il secondo indica il tipo di ricerca
|
|
def cosing_search(text : str, mode : str = "name") -> Union[list,dict,None]:
|
|
cosing_post_req = "https://api.tech.ec.europa.eu/search-api/prod/rest/search?apiKey=285a77fd-1257-4271-8507-f0c6b2961203&text=*&pageSize=100&pageNumber=1"
|
|
agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0"
|
|
|
|
# La modalità di ricerca base è quella per nome, che sia INCI o di altro tipo
|
|
if mode == "name":
|
|
search_query = {"bool":
|
|
{"must":[
|
|
{"text":
|
|
{"query":f"{text}","fields":
|
|
["inciName.exact","inciUsaName","innName.exact","phEurName","chemicalName","chemicalDescription"],
|
|
"defaultOperator":"AND"}}]}}
|
|
|
|
# In caso di ricerca per numero cas o EC il payload della richiesta è diverso
|
|
elif mode in ["cas","ec"]:
|
|
search_query = {"bool": {"must": [{"text": {"query": f"*{text}*","fields": ["casNo", "ecNo"]}}]}}
|
|
|
|
# La ricerca per ID è necessaria dove serva recuperare gli identified ingredients
|
|
elif mode == "id":
|
|
search_query = {"bool":{"must":[{"term":{"substanceId":f"{text}"}}]}}
|
|
|
|
# Se la mode inserita non è prevista lancio un errore
|
|
else:
|
|
raise ValueError
|
|
|
|
# Creo il payload della mia request
|
|
files = {"query": ("query",js.dumps(search_query),"application/json")}
|
|
|
|
# Eseguo la post di ricerca
|
|
risposta = req.post(cosing_post_req,headers={"user_agent":agent,"Connection":"keep-alive"},files=files)
|
|
risposta = risposta.json()
|
|
if risposta["results"]:
|
|
|
|
return risposta["results"][0]["metadata"]
|
|
|
|
# La funzione ritorna None quando non ho risultati dalla mia ricerca
|
|
return risposta.status_code
|
|
#endregion
|
|
|
|
#region Funzione per pulire un json cosing e restituirlo
|
|
|
|
def clean_cosing(json : dict, full : bool = True) -> dict:
|
|
|
|
# Definisco i campi di nostro interesse e li divido in base al tipo di output finale che desidero
|
|
|
|
string_cols = ["itemType","nameOfCommonIngredientsGlossary","inciName","phEurName","chemicalName","innName","substanceId","refNo"]
|
|
list_cols = ["casNo","ecNo","functionName","otherRestrictions","sccsOpinion","sccsOpinionUrls","identifiedIngredient","annexNo","otherRegulations"]
|
|
|
|
# Creo una lista con tutti i campi su cui ciclare
|
|
|
|
total_keys = string_cols + list_cols
|
|
|
|
# Definisco la base dell"url che mi serve per ottenere il link cosing della sostanza
|
|
|
|
base_url = "https://ec.europa.eu/growth/tools-databases/cosing/details/"
|
|
clean_json = {}
|
|
|
|
# Ciclo su tutti i campi di interesse
|
|
|
|
for key in total_keys:
|
|
|
|
# Alcuni campi contengono una dicitura inutile che occupa solo spazio
|
|
# per cui provvedo a rimuoverla
|
|
|
|
while "<empty>" in json[key]:
|
|
json[key].remove("<empty>")
|
|
|
|
# Se il campo dovrà avere in output una lista, posso anche accettare le liste vuote del cosing come valore
|
|
|
|
if key in list_cols:
|
|
value = json[key]
|
|
|
|
# Il cas e l"ec sono casi speciali, per cui eseguo delle funzioni in più quando lo tratto
|
|
|
|
if key in ["casNo", "ecNo"]:
|
|
if value:
|
|
value = parse_cas_numbers(value)
|
|
|
|
# Completiamo direttamente il json di ritorno ove presenti degli identifiedIngredient,
|
|
# solo dove il flag "full" è true
|
|
|
|
elif key == "identifiedIngredient":
|
|
if full:
|
|
if value:
|
|
value = identified_ingredients(value)
|
|
|
|
clean_json[key] = value
|
|
|
|
else:
|
|
|
|
# Questo nome di campo era troppo lungo e ho preferito semplificarlo
|
|
|
|
if key == "nameOfCommonIngredientsGlossary":
|
|
nKey = "commonName"
|
|
|
|
# Dovendo rinominare alcuni campi, negli altri casi copio il nome iniziale
|
|
|
|
else:
|
|
nKey = key
|
|
|
|
# Siccome voglio in output una stringa semplice e se usassi uno slicer su di una lista vuota
|
|
# devo prima verificare che la lista cosing contenga dei valori
|
|
|
|
if json[key]:
|
|
clean_json[nKey] = json[key][0]
|
|
else:
|
|
clean_json[nKey] = ""
|
|
|
|
# Il campo cosingUrl non esiste ancora, vado a crearlo unendo il substance ID all"url base
|
|
|
|
clean_json["cosingUrl"] = f"{base_url}{json["substanceId"][0]}"
|
|
|
|
return clean_json
|
|
#endregion
|
|
|
|
#region Funzione per completare, se necessario, un json cosing
|
|
|
|
def identified_ingredients(id_list : list) -> list:
|
|
|
|
identified = []
|
|
|
|
# Per ognuno degli ingredienti in IdentifiedIngredients eseguo una ricerca
|
|
|
|
for id in id_list:
|
|
|
|
ingredient = cosing_search(id,"id")
|
|
|
|
if ingredient:
|
|
|
|
# Vado a pulire i json appena trovati
|
|
|
|
ingredient = clean_cosing(ingredient,full=False)
|
|
|
|
# Ora salvo nella lista il documento pulito
|
|
|
|
identified.append(ingredient)
|
|
|
|
# Finito di popolare la lista con gli oggetti degli identifiedIngredient, la ritorno
|
|
|
|
return identified
|
|
#endregion |