06-12-2021
Cosa ho fatto
- Ho studiato tutta la tesi di Fabio Mariani.
- Ho studiato tutti gli script della repository meta:
- run_preprocess.py
- run_process.py
- scripts
- curator.py
- creator.py
- lib
- finder.py
- csvmanager.py
- id_manager
- issnmanager.py
- isbnmanager.y
- orcid_manager.py
- doimanager.py
- identifiermanager.pyy
- crossref
- crossrefProcessing.py
- orcid
- index_orcid_doi.py
- Ho raggruppato codice ripetuto in funzioni e rimosso alcune ripetizioni. Tutti i test sono stati eseguiti per verificare che non ci fossero bug e nuovi test sono stati aggiunti. Non riporto qui tutte le modifiche, perché si trovano nella pull request che ho preparato: https://github.com/opencitations/meta/pull/9
- Ho riflettuto su come salvare i csv restituiti in output dal Creator. Ecco un’idea:
- I dati vengono salvati in file csv di n righe, dove n è un numero arbitrario. Ogni file ha nome meta_[m].csv, dove m è un intero sequenziale, ad esempio meta_3.csv.
- Obiezione: come trovare rapidamente informazioni al loro interno?
- Soluzione: si utilizza un indice analico, dei file json in cui le chiavi sono i nomi delle entità e i valori sono set contenenti i nomi dei file in cui quelle entità sono contenute. Volendo, i nomi possono essere abbreviati in un set di interi.
- Obiezione: questa soluzione non è scalabile! Il file potrebbe raggiungere dimensioni astronomiche.
- Soluzione: gli indici vengono distribuiti in varie cartelle, una per ogni tipologia di entità. Ci saranno quindi le cartella ar, br, ra, re e meta. Ciascuna cartella contiene solo indici relativi a quel tipo di entità. Ogni indice al suo interno contiene k entità, dove k è un numero intero arbitrario. I nomi dei file degli indici sono parlanti. Un indice ha nome [start_end].json, ad esempio 1_1000.json è un indice che contiene le entità dalla 1 alla 1000. L’informazione sul tipo è nel nome della cartella e non viene ripetuta nel nome del file.
- Vantaggi:
- Gli indici permettono di trovare al volo i file csv desiderati a partire dall’URI delle entità.
- Gli indici possono essere generati a posteriori.
- Gli indici si possono aggiornare facilmente. È possibile aggiungere, rimuovere e modificare le informazioni al loro interno.
- Gli indici prescindono da come sono fatti i file csv. In particolare, prescindono dalle loro dimensioni e dal contenuto.
- È una soluzione scalabile.
- Svantaggi:
- Vengono generati dei file in più, l’informazione sulla posizione delle entità non è intrinseca nella nomenclatura dei file csv.
- Bisogna scrivere il codice per generare e aggiornare gli indici.
- Non conosco l’intero codice di OpenCitations, ma magari questo sistema entra in conflitto con un’altra parte del workflow.
- Forse non è quello che gli utenti vogliono.
- Ho cominciato a studiare software design pattern, partendo dai SOLID principles di Robert C. Martin (che per ragioni che mi sfuggono viene chiamato Uncle Bob).
Domande
La funzione **clean_id_list** ha due bug:
- Modifica la lista id_list mentre cicla su di essa. Questo causa un index error, che viene gestito tramite eccezione invece di essere risolto a monte.
- Non funziona se il prefisso META è scritto in maiuscolo (if “meta” in elem).
Riporto le modifiche che propongo a margine del codice originale.
Inoltre ho due domande:
- Sono previsti identificatori case-sensitive? Se no, si potrebbe mettere in minuscolo tutto quanto all’inizio anziché più volte com’è adesso.
- Perché se c’è più di un MetaID tutti i MetaIDs vengono rimossi dalla lista id_list? (if len(how_many_meta) > 1: )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34def clean_id_list(id_list, br=True):
if br:
pattern = "br/"
else:
pattern = "ra/"
metaid = ""
id_list = list(filter(None, id_list))
how_many_meta = [i for i in id_list if i.lower().startswith('meta')]
if len(how_many_meta) > 1:
for pos, elem in enumerate(id_list): # enumerate(list(id_list))
if "meta" in elem: # elem.lower()
id_list[pos] = "" # Perché?
else:
for pos, elem in enumerate(id_list): # enumerate(list(id_list))
try: # non serve
elem = Curator.string_fix(elem)
identifier = elem.split(":", 1)
value = identifier[1]
schema = identifier[0].lower()
if schema == "meta":
if "meta:" + pattern in elem:
metaid = value.replace(pattern, "")
else: # Non serve
id_list[pos] = ""
# id_list[pos] = "" Basta questo, tanto l'id viene comunque eliminato da id_list in caso contenga lo schema meta:
else:
newid = schema + ":" + value
id_list[pos] = newid
except IndexError: # Non serve
id_list[pos] = "" # Non serve
if metaid: # Non serve, è una ripetizione di id_list[pos] = "". Inoltre, è fragile perché non è detto che meta sia stato scritto in minuscolo.
id_list.remove("meta:" + pattern + metaid)
id_list = list(filter(None, id_list))
return id_list, metaidLa funzione clean_title ha un problema: se un titolo è tutto maiuscolo ma contiene acronimi, gli acronimi vengono erroneamente trasformati in minuscolo. È una scelta voluta?
In ogni caso, si potrebbe utilizzare un set di acronimi da controllare prima di eseguire title.lower(). Questo ulteriore passaggio non impatterebbe sull’efficienza della funzione, perché la ricerca in un set ha complessità O(1).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15def clean_title(title:str):
title = title.replace("\0", "")
if title.isupper():
title = title.lower()
# Mia proposta
# if not any(w in acronyms_set for w in title):
# title = title.lower()
words = title.split()
for pos, w in enumerate(words):
if any(x.isupper() for x in w):
pass
else:
words[pos] = w.title()
newtitle = " ".join(words)
return newtitleVorrei dedicare qualche minuto a riguardare insieme l’implementazione dell’albero decisionale (funzione id_worker). Ho l’impressione che l’implementazione non copra tutti i casi previsti dall’albero, ma potrei sbagliarmi.
L’implementazione volta a normalizzare il formato delle date è interessante. Ho capito come funziona parse_hack, ma non ho capito quale problema risolva l’euristica successiva che tenta di tagliare la stringa della data nel caso questa sia lunga 10 o 7 caratteri.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42try:
date = self.parse_hack(date)
except ValueError:
try:
if len(date) == 10:
try:
newdate = date[:-3]
date = self.parse_hack(newdate)
except ValueError:
try:
newdate = date[:-6]
date = self.parse_hack(newdate)
except ValueError:
date = ""
elif len(date) == 7:
try:
newdate = date[:-3]
date = self.parse_hack(newdate)
except ValueError:
date = ""
else:
date = ""
except ValueError:
date = ""
row['pub_date'] = date
@staticmethod
# hack dateutil automatic-today-date
def parse_hack(date):
dt = parse(date, default=datetime(2001, 1, 1))
dt2 = parse(date, default=datetime(2002, 2, 2))
if dt.year == dt2.year and dt.month == dt2.month and dt.day == dt2.day:
clean_date = parse(date).strftime("%Y-%m-%d")
elif dt.year == dt2.year and dt.month == dt2.month:
clean_date = parse(date).strftime("%Y-%m")
elif dt.year == dt2.year:
clean_date = parse(date).strftime("%Y")
else:
clean_date = ""
return clean_dateNon capisco cosa controllino le varie funzioni __checkdigit(), ad esempio quella in issnmanager.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18@staticmethod
def __check_digit(issn):
result_partial_sum = 0
for i, n in zip(range(8, 1, -1), issn[:4] + issn[5:8]):
result_partial_sum += i * int(n)
reminder = result_partial_sum % 11
reminder_sub = 11 - reminder
correct_check_digit = \
str(reminder_sub) == issn[8] or \
(reminder == 0 and issn[8] == "0") or \
(reminder_sub == 10 and issn[8] == "X")
result_full_sum = 0
for i, n in zip(range(8, 0, -1), issn[:4] + issn[5:]):
result_full_sum += i * (10 if n == "X" else int(n))
confirm_check_digit = result_full_sum % 11 == 0
return correct_check_digit and confirm_check_digitIn crossrefProcessing.py, come mai orcid_finder viene usato per gli autori e non per gli editor?
Nella sua tesi, Fabio dice che è possibile filtrare il dizionario risultante in output da csv_creator() in crossrefProcessing, al fine di conservare solo le entità il cui DOI è presente su COCI (pagina 88). Tuttavia, non trovo il codice per farlo.
ORCID è in ritardo sul rilascio del dump del 2021, che doveva uscire a Ottobre. Dovrò generare nuovamente l’indice doi-orcid quando sarà uscito?
Non ho trovato documenti riguardati il caso d’uso di OpenCitations Meta, che se ricordo bene ha riguardato Wikidata. Dove li trovo?
Domande per Fabio
- **clean_id_list:**
- Modifica la lista id_list mentre cicla su di essa. Questo causa un index error, che viene gestito tramite eccezione invece di essere risolto a monte.
- Non funziona se il prefisso META è scritto in maiuscolo (if “meta” in elem).
- Perché se c’è più di un MetaID tutti i MetaIDs vengono rimossi dalla lista id_list? (if len(how_many_meta) > 1: )
- Creare un test con due metaid uguali e diversi
- Vorrei dedicare qualche minuto a riguardare insieme l’implementazione dell’albero decisionale (funzione id_worker). Ho l’impressione che l’implementazione non copra tutti i casi previsti dall’albero, ma potrei sbagliarmi.
- Nel commento della riga 849 si legge che se c’è un id meta non ci si preoccupa di possibili conflitti. Leggendo l’albero però, è possibile arrivare a dei conflitti anche in presenza di un’id meta.
- In effetti, dal codice non capisco dove venga gestito il caso 3, perché non trovo il caso meta id + altri id già esistenti.
- Riga 962 e riga 996: che casi sono?
- A cosa serve il campo others nei dizionari delle entità? A contenere i wannabeid? È dove avviene il merge tra due linee che sono la stessa linea. Gli altri wannabe con cui è presente quell’entità.
- Nella funzione conflict, non dovrebbe essere retrieve_id(ids[1], ids[0])? I test non passano se li inverto.
- Riga 66: perché -= 1? Controllare testcase_13/test1
Appunti incontro
- Problema indici: grande mole di modifiche. Usiamo sempre json-ld. Produciamo csv solo alla fine.
- Guardare https://github.com/opencitations/WCW. Cercare convertitore in json
- crossrefProcessing.py output tanti CSV quanti sono i file di Crossref
- Esplorare set acronimi
- __check_digit è calcolato sulle cifre che lo precedono.
- Fare orcid_finder anche per gli editor
- Usare normalizer anche in Curator.py
- Aggiungere flag per validazione tramite API