# %% import csv import json import openpyxl as op # BASIC CONFIGURATION DATA_FOLDER = './data/' OUTPUT_FOLDER = './output/' ONTO_FILENAME = 'manoscritti_dariah' # No extension! ent_filename = ONTO_FILENAME + '_entities.csv' rel_filename = ONTO_FILENAME + '_relations.csv' # PART I: parse xlsx to (multiple) csv # Excel configuration XLSX_FILENAME = 'Struttura_NEW.xlsx' ENTITIES_SHEETNAME = 'Entità' RELATIONS_SHEETNAME = 'Relazioni' # %% # Import the defining xlsx through openpyxl input_data = op.load_workbook(DATA_FOLDER + XLSX_FILENAME) # Read relevant sheets entities_sheet = input_data[ENTITIES_SHEETNAME] relations_sheet = input_data[RELATIONS_SHEETNAME] # Parse sheet data into a dict (assuming the xlsx has headers) entities_keys = [cell for cell in next(entities_sheet.values) if cell] raw_entities = [{key: row[ind] for ind, key in enumerate(entities_keys)} for row in entities_sheet.values][1:] # relations_keys = [cell for cell in next(relations_sheet.values) if cell] raw_relations = [{key: row[ind] for ind, key in enumerate(relations_keys)} for row in relations_sheet.values][1:] # %% # NOTE: # a. Non ci sono, al momento, constraint di unicità imposti tramite il "foglio master" # 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 # 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. # 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. # 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. # f. Si effettuano controlli di consistenza sulle relazioni: # .f1. Nessuna relazione con entità non definite # .f2. Nessuna entità "orfana", ovvero non presente in alcuna relazione # # TODO: completare secondo le specifiche sopra # TODO: effettuare il merge con i "miei" CSV, che hanno informazioni in più! # Process entities: # 1. Filter out unnamed entities, find out aliases, normalize entity names, find out duplicates clean_entities = {} for ent in raw_entities: entity_names = ent['Concetto'] if not isinstance(entity_names, str): continue aliases = [al.strip().title() for al in entity_names.split('\n') if al.strip()] if not aliases: continue entity_name = aliases[0] entity_same_as = aliases[1:] if clean_entities.get(entity_name): # DUPLICATE! clean_entities[entity_name].append({'Alias': aliases, 'Raw': ent}) else: clean_entities[entity_name] = [{'Alias': aliases, 'Raw': ent}] all_entities = clean_entities.keys() duplicated_entities = [ent_name for ent_name, ent_val in clean_entities.items() if len(ent_val)>1] # %% # Process relations: # 1. Filter ill-formed relations and normalize entity names clean_relations = [] for rel in raw_relations: subj = rel['Soggetto'] obj = rel['Oggetto'] if not isinstance(subj, str) or not isinstance(obj, str): continue subj = subj.strip().title() obj = obj.strip().title() if subj==obj: continue rel_name = rel['Relazione'] if isinstance(rel_name, str): rel_name = rel_name.strip().lower() better_rel = {'Soggetto': subj, 'Relazione': rel_name, 'Oggetto': obj, 'Pair': tuple(set([subj, obj]))} clean_relations.append(better_rel) all_pairs = [rel['Pair'] for rel in clean_relations] all_cited_entities = set(sum([[rel['Soggetto'], rel['Oggetto']] for rel in clean_relations], [])) undefined_entities = all_cited_entities - all_entities # %%