diff --git a/app/core/admin.py b/app/core/admin.py index 8bbdb62..0e95d59 100644 --- a/app/core/admin.py +++ b/app/core/admin.py @@ -142,23 +142,13 @@ def get_form(self, request, obj=None, **kwargs): class NeoTermAdminForm(forms.ModelForm): alias = forms.CharField(required=False, help_text="Enter alias") # Custom field definition = forms.CharField(required=True, help_text="Enter definition") # Custom field - context = forms.CharField(required=True, help_text="Enter context") # Custom field - context_description = forms.CharField(required=True, help_text="Enter context description") # Custom field + context = forms.CharField(required=False, help_text="Enter context") # Custom field + context_description = forms.CharField(required=False, help_text="Enter context description") # Custom field class Meta: model = NeoTerm fields = ['lcvid', 'alias', 'definition', 'context', 'context_description'] - # def clean_definition(self): - # definition = self.cleaned_data.get('definition') - - # get_terms_with_multiple_definitions() - # # Check if the definition already exists in the NeoDefinition model - # if is_any_node_present(NeoDefinition, definition=definition): - # raise forms.ValidationError(f"A definition of '{definition}' already exists.") - - # return definition # Return the cleaned value - class NeoTermAdmin(admin.ModelAdmin): form = NeoTermAdminForm list_display = ('lcvid', 'uid') @@ -177,8 +167,17 @@ def save_model(self, request, obj, form, change): definition = form.cleaned_data['definition'] context = form.cleaned_data['context'] context_description = form.cleaned_data['context_description'] - logger.info(f"Creating NeoTerm with alias: {alias}, definition: {definition}, context: {context}, context_description: {context_description}") + + logger.info(f"Creating NeoTerm with alias: {alias}, definition: {definition}, context: {context}, context_description: {context_description}") + if context == '' and context_description == '' and alias != '': + definition_node = NeoDefinition.nodes.get_or_none(definition=definition) + if definition_node: + messages.warning(request, 'Adding an alias without a context is not recommended.') + run_node_creation(alias=alias, definition=definition, context=context, context_description=context_description) + return + messages.error(request, 'Adding a definition without a context is not allowed.') + return run_node_creation(alias=alias, definition=definition, context=context, context_description=context_description) messages.success(request, 'NeoTerm saved successfully.') @@ -187,7 +186,6 @@ def save_model(self, request, obj, form, change): logger.error('Error saving NeoTerm: {}'.format(e)) messages.error(request, 'Error saving NeoTerm: {}'.format(e)) return - def delete_model(self, request, obj) -> None: messages.error(request, 'Deleting terms is not allowed') diff --git a/app/core/models.py b/app/core/models.py index d0c39a7..148205f 100644 --- a/app/core/models.py +++ b/app/core/models.py @@ -472,25 +472,17 @@ def create_new_term(cls, lcvid: str = None) -> 'NeoTerm': return term_node - def set_relationships(self, definition_node, context_node, alias_node): + def set_relationships(self, definition_node=None, context_node=None, alias_node=None): try: if alias_node: self.alias.connect(alias_node) - self.context.connect(context_node) - self.definition.connect(definition_node) + if context_node: + self.context.connect(context_node) + if definition_node: + self.definition.connect(definition_node) except exceptions.NeomodelException as e: logger.error(f"NeoModel-related error while connecting relationships for term '{self.uid}': {e}") raise e - -# @abstract -# class NeoGeneric(DjangoNode): - -# def are_any_nodes_present(self, model, **kwargs): -# pass -# def connect(self, node, relationship): -# pass - - class NeoAlias(DjangoNode): django_id = UniqueIdProperty() alias = StringProperty(unique_index=True,required=True) @@ -520,16 +512,25 @@ def get_or_create(cls, alias: str) -> Tuple['NeoAlias', bool]: logger.error(f"Unexpected error in get_or_create for alias '{alias}': {e}") raise e - def set_relationships(self, term_node, context_node): + def set_relationships(self, term_node=None, context_node=None, collided_definition=None): try: - self.term.connect(term_node) - self.context.connect(context_node) + if term_node: + self.term.connect(term_node) + if context_node: + self.context.connect(context_node) + if collided_definition: + self.collided_definition.connect(collided_definition) except exceptions.NeomodelException as e: logger.error(f"NeoModel-related error while connecting relationships for alias '{self.alias}': {e}") raise e except Exception as e: logger.error(f"Unexpected error while connecting relationships for alias '{self.alias}': {e}") raise e + + def handle_collision(self, definition_node, context_node=None): + if context_node: + self.context.connect(context_node) + self.collided_definition.connect(definition_node) class NeoContext(DjangoNode): django_id = UniqueIdProperty() @@ -545,32 +546,30 @@ class Meta: @classmethod def get_or_create(cls, context: str) -> Tuple['NeoContext', bool]: try: - context_node = cls.nodes.get_or_none(context=context) - if context_node: return context_node, False - - context_node = NeoContext(context=context) - context_node.save() - return context_node, True - + if not context == '': + context_node = NeoContext(context=context) + context_node.save() + return context_node, True except exceptions.NeomodelException as e: logger.error(f"NeoModel-related error while getting or creating context '{context}': {e}") raise e except Exception as e: - logger.error(e) logger.error(f"Unexpected error in get_or_create for context '{context}': {e}") raise e - def set_relationships(self, term_node, alias_node, definition_node, context_description_node): + def set_relationships(self, term_node=None, alias_node=None, definition_node=None, context_description_node=None,): try: - self.term.connect(term_node) + if term_node: + self.term.connect(term_node) if alias_node: self.alias.connect(alias_node) - self.definition.connect(definition_node) - logger.info(f"Connecting context_description_node: {context_description_node}") - self.context_description.connect(context_description_node) + if definition_node: + self.definition.connect(definition_node) + if context_description_node: + self.context_description.connect(context_description_node) except exceptions.NeomodelException as e: logger.error(f"NeoModel-related error while connecting relationships for context '{self.context}': {e}") raise e @@ -588,16 +587,14 @@ class Meta: app_label = 'core' @classmethod - def get_or_create(cls, context_description: str, context_node: NeoContext): + def get_or_create(cls, context_description: str, context_node: 'NeoContext'): try: - existing_context_description = context_node.context_description.all() - if existing_context_description: - return existing_context_description[0], False - - context_description_node = NeoContextDescription(context_description=context_description) + existing = context_node.context_description.all() if context_node else [] + if existing: + return existing[0], False + context_description_node = cls(context_description=context_description) context_description_node.save() return context_description_node, True - except exceptions.NeomodelException as e: logger.error(f"NeoModel-related error while getting or creating context_description '{context_description}': {e}") raise e @@ -605,10 +602,12 @@ def get_or_create(cls, context_description: str, context_node: NeoContext): logger.error(f"Unexpected error in get_or_create for context_description '{context_description}': {e}") raise e - def set_relationships(self, definition_node, context_node): + def set_relationships(self, definition_node=None, context_node=None): try: - self.definition.connect(definition_node) - self.context.connect(context_node) + if definition_node: + self.definition.connect(definition_node) + if context_node: + self.context.connect(context_node) except exceptions.NeomodelException as e: logger.error(f"NeoModel-related error while connecting relationships for context_description '{self.context_description}': {e}") raise e @@ -623,7 +622,7 @@ class NeoDefinition(DjangoNode): rejected = BooleanProperty(default=False) context = RelationshipTo('NeoContext', 'VALID_IN') context_description = RelationshipFrom('NeoContextDescription', 'BASED_ON') - term = RelationshipFrom('NeoTerm', 'POINTS_TO') + term = Relationship('NeoTerm', 'POINTS_TO') collision = Relationship('NeoDefinition', 'IS_COLLIDING_WITH') collision_alias = Relationship('NeoAlias', 'WAS_ADDED_WITH') @@ -642,12 +641,26 @@ def get_or_create(cls, definition:str, definition_embedding=None): except Exception as e: logger.error(f"Error in get for NeoDefinition '{definition}': {e}") + raise e + + def get_term_node(self)-> 'NeoTerm': + if self.term: + logger.info(f'The data is: {self.term.single()}') + return self.term.single() + return None - def set_relationships(self, term_node, context_node, context_description_node): + def set_relationships(self, term_node=None, context_node=None, context_description_node=None, collision=None, collision_alias=None): try: - self.term.connect(term_node) - self.context.connect(context_node) - self.context_description.connect(context_description_node) + if term_node: + self.term.connect(term_node) + if context_node: + self.context.connect(context_node) + if context_description_node: + self.context_description.connect(context_description_node) + if collision: + self.collision.connect(collision) + if collision_alias: + self.collision_alias.connect(collision_alias) except exceptions.NeomodelException as e: logger.error(f"NeoModel-related error while connecting relationships for definition '{self.definition}': {e}") raise e diff --git a/app/core/urls.py b/app/core/urls.py index b13d400..06a7f0b 100644 --- a/app/core/urls.py +++ b/app/core/urls.py @@ -4,5 +4,4 @@ urlpatterns = [ path('neo_term_list/', views.get_neo_terms, name='neo_term_list'), - ] diff --git a/app/core/utils.py b/app/core/utils.py index abef93f..3468f0b 100644 --- a/app/core/utils.py +++ b/app/core/utils.py @@ -15,7 +15,7 @@ def run_node_creation(definition: str, context: str, context_description: str, a if deconfliction_status == 'unique': run_unique_definition_creation(definition=definition, context=context, context_description=context_description, definition_embedding=definition_vector_embedding, alias=alias) elif deconfliction_status == 'duplicate': - run_duplicate_definition_creation(alias, most_similar_text, context, context_description) + run_duplicate_definition_creation(alias=alias, definition=most_similar_text, context=context, context_description=context_description) elif deconfliction_status == 'collision': run_collision_definition_creation(alias, most_similar_text, definition, context, context_description, definition_vector_embedding, highest_score) @@ -27,64 +27,57 @@ def run_node_creation(definition: str, context: str, context_description: str, a def run_unique_definition_creation(definition, context, context_description, definition_embedding, alias): try: - # uid = uuid4() term_node = NeoTerm.create_new_term() - - alias_node = None - if alias: - alias_node, _ = NeoAlias.get_or_create(alias=alias) - + alias_node, _ = NeoAlias.get_or_create(alias=alias) if alias else (None, None) definition_node, _ = NeoDefinition.get_or_create(definition=definition, definition_embedding=definition_embedding) - context_node, _ = NeoContext.get_or_create(context=context) - context_description_node, _ = NeoContextDescription.get_or_create(context_description=context_description, context_node=context_node) - context_node.set_relationships(term_node=term_node, alias_node=alias_node, definition_node=definition_node, context_description_node=context_description_node) term_node.set_relationships(alias_node=alias_node, definition_node=definition_node, context_node=context_node) + context_node.set_relationships(term_node=term_node, alias_node=alias_node, definition_node=definition_node, context_description_node=context_description_node) + definition_node.set_relationships(term_node=term_node, context_node=context_node, context_description_node=context_description_node) + context_description_node.set_relationships(definition_node=definition_node, context_node=context_node) + if alias_node: alias_node.set_relationships(term_node=term_node, context_node=context_node) - definition_node.set_relationships(term_node, context_node, context_description_node) - context_description_node.set_relationships(definition_node, context_node) - - except Exception as e: + + except Exception as e: logger.error(f"Error in run_unique_definition_creation: {e}") - raise e - + raise + def run_duplicate_definition_creation(alias, definition, context, context_description): try: - - alias_node = None - alias__ = False - if alias: - alias_node, _ = NeoAlias.get_or_create(alias=alias) - logger.info(f"Alias Node: {alias_node}") - - context_node, _ = NeoContext.get_or_create(context=context) - logger.info(f"Context Node: {context_node}") - context_description_node, _ = NeoContextDescription.get_or_create(context_description=context_description, context_node=context_node) - logger.info(f"Context Description Node: {context_description_node}") - + alias_node, _ = NeoAlias.get_or_create(alias=alias) if alias else (None, None) + context_node, _ = NeoContext.get_or_create(context=context) if context else (None, None) + context_description_node, _ = NeoContextDescription.get_or_create(context_description=context_description, context_node=context_node) if context_description else (None, None) definition_node, _ = NeoDefinition.get_or_create(definition=definition) - term_node = definition_node.term.single() - if not term_node: - context_node.alias.connect(alias_node) - context_node.context_description.connect(context_description_node) - context_node.definition.connect(definition_node) - alias_node.context.connect(context_node) - alias_node.collided_definition.connect(definition_node) - definition_node.context.connect(context_node) - definition_node.context_description.connect(context_description_node) + + term_node = definition_node.get_term_node() + logger.info(term_node) + if not term_node: # Duplicate collision scenario + if alias_node: + alias_node.set_relationships(collided_definition=definition_node, context_node=context_node) + if context_node: + context_node.set_relationships(alias_node=alias_node, definition_node=definition_node, context_description_node=context_description_node) + definition_node.set_relationships(context_node=context_node, context_description_node=context_description_node) + context_description_node.set_relationships(definition_node=definition_node, context_node=context_node) return - context_node.set_relationships(term_node=term_node, alias_node=alias_node, definition_node=definition_node, context_description_node=context_description_node) - logger.info(f"Context Node Relationships: {context_node}") - logger.info(f"Alias Node Relationships: {alias_node}") - term_node.set_relationships(alias_node=alias_node, definition_node=definition_node, context_node=context_node) + + # Duplicate scenario with a term node (acts like unique scenario) + term_node.set_relationships(alias_node=alias_node, definition_node=definition_node) + if context_node: + context_node.set_relationships(term_node=term_node, alias_node=alias_node, definition_node=definition_node, context_description_node=context_description_node) + + definition_node.set_relationships(term_node=term_node, context_node=context_node, context_description_node=context_description_node) + if context_description_node: + context_description_node.set_relationships(definition_node=definition_node, context_node=context_node) + if alias_node: - alias_node.set_relationships(term_node, context_node) - definition_node.set_relationships(term_node, context_node, context_description_node) + if not context_node: + alias_node.set_relationships(term_node=term_node) + alias_node.set_relationships(term_node=term_node, context_node=context_node) - except Exception as e: + except Exception as e: logger.error(f"Error in run_duplicate_definition_creation: {e}") raise e @@ -93,22 +86,22 @@ def run_collision_definition_creation(alias, most_similar_definition, definition alias_node = None if alias: alias_node, _ = NeoAlias.get_or_create(alias=alias) - existing_definition_node = NeoDefinition.nodes.get(definition=most_similar_definition) - colliding_definition_node = NeoDefinition(definition=definition, embedding=definition_vector_embedding) - colliding_definition_node.save() + existing_definition_node, _ = NeoDefinition.get_or_create(definition=most_similar_definition) + if not existing_definition_node: + logger.error('Existing definition node not found') + raise Exception('Existing definition node not found') + colliding_definition_node, _ = NeoDefinition.get_or_create(definition=definition, definition_embedding=definition_vector_embedding) + logger.info(f"Colliding Definition Node: {colliding_definition_node}") + if not colliding_definition_node: + logger.error('Colliding definition node not found') + raise Exception('Colliding definition node not found') context_node, _ = NeoContext.get_or_create(context=context) - context_description_node, _ = NeoContextDescription.get_or_create(context_description=context_description, context_node=context_node) - alias_node.context.connect(context_node) - context_node.context_description.connect(context_description_node) - context_node.definition.connect(colliding_definition_node) - context_description_node.definition.connect(colliding_definition_node) - colliding_definition_node.context.connect(context_node) - colliding_definition_node.context_description.connect(context_description_node) - alias_node.collided_definition.connect(colliding_definition_node) - colliding_definition_node.collision_alias.connect(alias_node) - colliding_definition_node.collision.connect(existing_definition_node) + alias_node.set_relationships(context_node=context_node, collided_definition=colliding_definition_node) + context_node.set_relationships(context_description_node=context_description_node, definition_node=colliding_definition_node) + context_description_node.set_relationships(definition_node=colliding_definition_node) + colliding_definition_node.set_relationships(context_node=context_node, context_description_node=context_description_node, collision_alias=alias_node, collision=existing_definition_node) except Exception as e: logger.error(f"Error in run_collision_definition_creation: {e}") diff --git a/app/uid/forms.py b/app/uid/forms.py index 2077103..fdf647e 100644 --- a/app/uid/forms.py +++ b/app/uid/forms.py @@ -1,7 +1,9 @@ from django import forms from .models import Provider, LCVTerm, UIDRequestToken # Import Neo4j models directly from uuid import uuid4 +# from .models import Alias # Import Neo4j models directly #from .models import LastGeneratedUID +from .models import NeoAliasManager #class LastGeneratedUIDForm(forms.ModelForm): # class Meta: @@ -70,3 +72,14 @@ class Meta: model = LCVTerm #fields = ['uid', 'term', 'echelon_level'] fields = ['provider_name', 'term', 'echelon', 'structure'] # UID is self Generated + +# Search Forms +class SearchForm(forms.Form): + search_term = forms.CharField(max_length=255, required=True, label="Search Term") + search_type = forms.ChoiceField(choices=[ + ('alias', 'Search by Alias'), + ('definition', 'Search by Definition'), + ('context', 'Search by Context'), + ], required=True, label="Search Type" + ) + context = forms.CharField(label='Context', required=False, max_length=255) diff --git a/app/uid/models.py b/app/uid/models.py index b932025..0d2466b 100644 --- a/app/uid/models.py +++ b/app/uid/models.py @@ -7,6 +7,9 @@ from typing import List from uuid import uuid4 +from collections import defaultdict +# from core.models import NeoTerm + logger = logging.getLogger(__name__) GLOBAL_PROVIDER_OWNER_UID = "0xFFFFFFFF" @@ -23,6 +26,90 @@ def check_neo4j_connection(): time.sleep(1) # Wait before retrying return False +# Alias class incase you create and alias with no context +# class Alias(StructuredNode): +# alias = StringProperty(unique_index=True) # The alias name +# context = StringProperty(required=False, default=None) # Optional context +# points_to = RelationshipTo('NeoTerm', 'POINTS_TO') # The relationship to NeoTerm +# context_error = StringProperty(required=False) # Optional field to store error message + +# def __str__(self): +# return self.alias + +# def link_to_term(self, neo_term): +# from core.models import NeoTerm, NeoAlias, NeoContext +# """Link this alias to a NeoTerm.""" +# if isinstance(neo_term, NeoTerm): +# self.points_to.connect(neo_term) + +# def save(self, *args, **kwargs): +# """Override the save method to automatically link the alias to a NeoTerm if context is provided.""" +# context_error = None # Initialize an error variable + +# # Call the parent class save method +# super(Alias, self).save(*args, **kwargs) + +# if self.context: +# from core.models import NeoTerm, NeoAlias, NeoContext +# term, created = NeoTerm.get_or_create(uid=self.context) # Get or create the NeoTerm based on the context +# if term: +# # Set relationships for the NeoTerm, including the alias +# term.set_relationships(definition_node, context_node, self) +# else: +# context_error = f"No matching NeoTerm found for context: {self.context}" +# else: +# # If no context is provided, link to a default NeoTerm (first available NeoTerm) +# term = NeoTerm.nodes.first() # You can change this to a specific fallback logic +# if term: +# self.link_to_term(term) +# else: +# context_error = "No NeoTerm available to link." + +# # If an error was encountered, raise it so it can be caught in the view or returned to the form +# if context_error: +# self.context_error = context_error # Store the error message in the instance +# self.save() + +# return context_error # Return the error message, if any + +# Addition of the NeoAliasManager class to use NeoAlias in core/models +class NeoAliasManager: + @staticmethod + def link_alias_to_term_and_context(alias: str, context: str = None): + from core.models import NeoTerm, NeoAlias, NeoContext + """Manage the linking of NeoAlias to NeoTerm and NeoContext.""" + context_error = None + + # Get or create the NeoAlias (same as get_or_create in your Alias class) + alias_node, created = NeoAlias.get_or_create(alias) + + if context: + # If context is provided, attempt to get or create NeoContext + context_node, context_created = NeoContext.get_or_create(context) + if context_node: + # Link the alias to the context + alias_node.context.connect(context_node) + else: + context_error = f"No matching NeoContext found for context: {context}" + + if not alias_node.term: + # If no term is linked, link the alias to the first available NeoTerm + term = NeoTerm.nodes.first() # Fallback logic to link to the first available NeoTerm + if term: + alias_node.term.connect(term) + else: + context_error = context_error or "No NeoTerm available to link." + + # Save the alias (optional) + alias_node.save() + + # If any errors occurred, return the error message + if context_error: + alias_node.context_error = context_error # Store error on the alias + alias_node.save() # Save the alias again with the error information + + return context_error # Return the error message, if any + # Generated Logs to track instance, time of generation, uid, provider and lcv terms class GeneratedUIDLog(models.Model): uid = models.CharField(max_length=255, default="UNKNOWN") @@ -512,4 +599,4 @@ def report_all_term_uids(): # # Find collisions (where length > 1) # collisions = {key: value for key, value in uid_dict.items() if len(value) > 1} -# return collisions \ No newline at end of file +# return collisions diff --git a/app/uid/templates/create_alias.html b/app/uid/templates/create_alias.html new file mode 100644 index 0000000..ca6eae3 --- /dev/null +++ b/app/uid/templates/create_alias.html @@ -0,0 +1,44 @@ + + +
+UID | +Alias | +Definition | +Context | +
---|---|---|---|
{{ record.LCVID }} | +{{ record.Alias }} | +{{ record.Definition }} | +{{ record.Context|default:"No context" }} | +