the_one_that_does_it.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. # %%
  2. import csv
  3. import json
  4. import openpyxl as op
  5. import re
  6. # BASIC CONFIGURATION
  7. DATA_FOLDER = './data/'
  8. OUTPUT_FOLDER = './output/'
  9. ONTO_FILENAME = 'manoscritti_dariah' # No extension!
  10. ent_filename = ONTO_FILENAME + '_entities.csv'
  11. rel_filename = ONTO_FILENAME + '_relations.csv'
  12. # PART I: parse xlsx to (multiple) csv
  13. # Excel configuration
  14. XLSX_FILENAME = 'Struttura_NEW.xlsx'
  15. ENTITIES_SHEETNAME = 'Entità'
  16. RELATIONS_SHEETNAME = 'Relazioni'
  17. # %%
  18. # Import the defining xlsx through openpyxl
  19. input_data = op.load_workbook(DATA_FOLDER + XLSX_FILENAME)
  20. # Read relevant sheets
  21. entities_sheet = input_data[ENTITIES_SHEETNAME]
  22. relations_sheet = input_data[RELATIONS_SHEETNAME]
  23. # Parse sheet data into a dict (assuming the xlsx has headers)
  24. entities_keys = [cell for cell in next(entities_sheet.values) if cell]
  25. raw_entities = [{key: row[ind] for ind, key in enumerate(entities_keys)} for row in entities_sheet.values][1:]
  26. #
  27. relations_keys = [cell for cell in next(relations_sheet.values) if cell]
  28. raw_relations = [{key: row[ind] for ind, key in enumerate(relations_keys)} for row in relations_sheet.values][1:]
  29. # %%
  30. # NOTE:
  31. # a. Non ci sono, al momento, constraint di unicità per le relazioni imposti tramite il "foglio master"
  32. # b. Non ci sono neanche constraint di esistenza, TRANNE l'id univoca, intesa NON come quella del sistema ma quella della comunità di riferimento, e tipica del settore di dominio considerato
  33. # c. Per identificare le informazioni 'atomiche' non si usa un campo dedicato, ma una logica. AL MOMENTO la logica è che si considera atomica una entità che non è mai 'prima' in una relazione. L'ORDINE DELLE RELAZIONI E' IMPORTANTE a differenza di quanto assumevo inizialmente.
  34. # d. Si effettua un controllo di unicità sulle entità, basato sul nome normalizzato (parole con iniziale maiuscola e il resto minuscolo, spazi ridotti a spazio singolo, strip()). Nessuna entità può avere nome vuoto.
  35. # e. Si effettua un controllo di unicità sulle relazioni, che però riguarda tutta la terna SOGGETTO-RELAZIONE-OGGETTO (normalizzata in modo simile ai nomi di entità, ma nella RELAZIONE gli spazi sono underscores e si usa lower() invece che title()). Nessuno dei membri della terna può essere vuoto; il nome della relazione inversa è opzionale.
  36. # f. Si effettuano controlli di consistenza sulle relazioni:
  37. # .f1. Nessuna relazione con entità non definite
  38. # .f2. Nessuna entità "orfana", ovvero non presente in alcuna relazione
  39. #
  40. # TODO: completare secondo le specifiche sopra
  41. # TODO: effettuare il merge con i "miei" CSV, che hanno informazioni in più!
  42. # TODO: ottimizzare un po' la scrittura del codice
  43. # Process entities:
  44. # 1. Filter out unnamed entities, normalize entity names, collect aliases, discover duplicates
  45. clean_entities = {}
  46. for ent in raw_entities:
  47. entity_names = ent['Concetto']
  48. if not isinstance(entity_names, str):
  49. continue
  50. aliases = [re.sub(r'\s+', ' ', al.strip().title()) for al in entity_names.split('\n') if al.strip()]
  51. if not aliases:
  52. continue
  53. entity_name = aliases[0]
  54. entity_same_as = aliases[1:]
  55. if clean_entities.get(entity_name):
  56. # DUPLICATE!
  57. clean_entities[entity_name].append({'Alias': aliases, 'Raw': ent})
  58. else:
  59. clean_entities[entity_name] = [{'Alias': aliases, 'Raw': ent}]
  60. all_entities = clean_entities.keys()
  61. duplicated_entities = [ent_name for ent_name, ent_val in clean_entities.items() if len(ent_val)>1]
  62. # %%
  63. # Process relations:
  64. # 1. Filter ill-formed relations and normalize entity names
  65. clean_relations = []
  66. for rel in raw_relations:
  67. subj = rel['Soggetto']
  68. obj = rel['Oggetto']
  69. if not isinstance(subj, str) or not isinstance(obj, str):
  70. continue
  71. subj = re.sub(r'\s+', ' ', subj.strip().title())
  72. obj = re.sub(r'\s+', ' ', obj.strip().title())
  73. if subj==obj:
  74. continue
  75. rel_name = rel['Relazione']
  76. if isinstance(rel_name, str):
  77. rel_name = re.sub(r'\s+', '_', rel_name.strip().lower()).replace('__', '_')
  78. clean_rel = {'Soggetto': subj, 'Relazione': rel_name, 'Oggetto': obj}
  79. clean_relations.append(clean_rel)
  80. all_rels = set((rel['Soggetto'], rel['Relazione'], rel['Oggetto']) for rel in clean_relations)
  81. all_subjects = set(rel['Soggetto'] for rel in clean_relations)
  82. all_cited_entities = set(sum([[rel['Soggetto'], rel['Oggetto']] for rel in clean_relations], []))
  83. undefined_entities = all_cited_entities - all_entities
  84. unused_entities = all_entities - all_cited_entities
  85. atomic_entities = all_cited_entities - all_subjects
  86. problematic_entities = undefined_entities - atomic_entities
  87. # %%
  88. ####
  89. # MANUS ONLINE (MOL) API: https://api.iccu.sbn.it/devportal/apis
  90. ####
  91. # %%
  92. for ent in sorted(list(problematic_entities)):
  93. print(ent)
  94. # %%
  95. for ent in sorted(list(atomic_entities)):
  96. print(ent)
  97. # %%
  98. for ent in sorted(list(unused_entities)):
  99. print(ent)
  100. # %%
  101. for ent in sorted(list(undefined_entities)):
  102. print(ent)
  103. # %%