Loading src/api/main.py +21 −21 Original line number Diff line number Diff line Loading @@ -409,22 +409,22 @@ class Api: def update_network_slice_service(self, intent): """ Modifica (reemplaza) toda la configuración de network-slice-services Modify (replace) all network-slice-services configuration """ try: xpath = "/ietf-network-slice-service:network-slice-services" # Verificar que existe algo que modificar # Verify if there is something to modify existing_data = get_data_store(xpath) if not existing_data: return send_response(False, code=404, message="Network slice services not found") # Si no está en modo DUMMY, procesar con TFS # If not in DUMMY mode, process with TFS result = self.slice_service.nsc(intent) if not result: return send_response(False, code=500, message="Failed to process slice in TFS") # Reemplazar completamente el recurso # Replace completely the resource update_data_store(intent) logging.info("Network slice services modified successfully") Loading @@ -442,17 +442,17 @@ class Api: def update_slo_sle_template(self, template_id, template): """ Modifica (reemplaza) un template SLO/SLE específico Modify (replace) an specific SLO/SLE template """ try: xpath = f"/ietf-network-slice-service:network-slice-services/slo-sle-templates/slo-sle-template[id='{template_id}']" # Verificar que el template existe # Verify the template exists existing_template = get_data_store(xpath) if not existing_template: return send_response(False, code=404, message="Template not found") # Asegurar que el ID en el body coincide con el de la URL # Assure that the body ID matches the URL if "id" in template and template["id"] != template_id: return send_response(False, code=400, message="Template ID in body does not match URL") Loading @@ -474,11 +474,11 @@ class Api: if not result: return send_response(False, code=500, message="Slice not updated") # Eliminar el ID del body si existe (ya está en el predicado) # Remove the ID from the body if it exists (it's already in the predicate) template_data = template.copy() template_data.pop("id", None) # Reemplazar el template # Replace the template update_data_store(template_data, xpath) logging.info(f"Template {template_id} modified successfully") Loading @@ -501,16 +501,16 @@ class Api: try: xpath = f"/ietf-network-slice-service:network-slice-services/slice-service[id='{slice_id}']" # Verificar que el slice existe # Verify that the slice exists existing_slice = get_data_store(xpath) if not existing_slice: return send_response(False, code=404, message="Slice not found") # Asegurar que el ID en el body coincide con el de la URL # Assure that the body ID matches the URL if "id" in intent and intent["id"] != slice_id: return send_response(False, code=400, message="Slice ID in body does not match URL") # Validar que existe el template SLO/SLE referenciado # Validate that the referenced SLO/SLE template exists if "slo-sle-template" in intent: template_ref = intent.get("slo-sle-template") xpath_template = f"/ietf-network-slice-service:network-slice-services/slo-sle-templates/slo-sle-template[id='{template_ref}']" Loading @@ -518,7 +518,7 @@ class Api: if not existing_template: return send_response(False, code=404, message="Referenced SLO/SLE template not found") # Construir el intent completo para TFS # Build the full intent full_intent = { "ietf-network-slice-service:network-slice-services": { "slo-sle-templates": { Loading @@ -544,11 +544,11 @@ class Api: if not result: return send_response(False, code=500, message="Slice not updated") # Eliminar el ID del body (ya está en el predicado) # Remove the ID from the body (it's already in the predicate) intent_data = intent.copy() intent_data.pop("id", None) # Reemplazar el slice # Replace the slice update_data_store(intent_data, xpath) logging.info(f"Slice {slice_id} modified successfully") Loading @@ -568,30 +568,30 @@ class Api: def update_sdp(self, slice_id, sdp_id, sdp): """ Modifica (reemplaza) un SDP específico dentro de un slice Modify (replace) an specific SDP in the slice """ try: # Verificar que el slice existe # Verify the template exists slice_xpath = f"/ietf-network-slice-service:network-slice-services/slice-service[id='{slice_id}']" existing_slice = get_data_store(slice_xpath) if not existing_slice: return send_response(False, code=404, message="Slice not found") # Verificar que el SDP existe # Verify the SDP exists sdp_xpath = f"/ietf-network-slice-service:network-slice-services/slice-service[id='{slice_id}']/sdps/sdp[id='{sdp_id}']" existing_sdp = get_data_store(sdp_xpath) if not existing_sdp: return send_response(False, code=404, message="SDP not found") # Asegurar que el ID en el body coincide con el de la URL # Assure that the body ID matches the URL if "id" in sdp and sdp["id"] != sdp_id: return send_response(False, code=400, message="SDP ID in body does not match URL") # Eliminar el ID del body (ya está en el predicado) # Remove the ID from the body (it's already in the predicate) sdp_data = sdp.copy() sdp_data.pop("id", None) # Reemplazar el SDP # Replace the SDP update_data_store(sdp_data, sdp_xpath) logging.info(f"SDP {sdp_id} in slice {slice_id} modified successfully") Loading src/database/sysrepo_store.py +42 −42 Original line number Diff line number Diff line Loading @@ -21,13 +21,13 @@ import logging def _get_connection(): """ Crea una nueva conexión cada vez Creates a new connection each time """ return sysrepo.SysrepoConnection() def create_data_store(intent: dict, xpath: str= ""): """ CREATE: Crea nuevos datos en el datastore CREATE: Creates new data in the datastore """ conn = _get_connection() sess = conn.start_session() Loading @@ -48,7 +48,7 @@ def create_data_store(intent: dict, xpath: str= ""): def get_data_store(xpath: str = ""): """ Obtiene todos los slices del datastore Gets all slices from the datastore """ conn = _get_connection() sess = conn.start_session() Loading @@ -69,23 +69,23 @@ def get_data_store(xpath: str = ""): def update_data_store(intent: dict, xpath: str = ""): """ UPDATE: Modifica datos en el datastore UPDATE: Modifies data in the datastore Args: intent: Datos a modificar xpath: Ruta al recurso intent: Data to modify xpath: Path to resource operation: - "merge" (default): PATCH - actualiza campos específicos - "replace": PUT - reemplaza completamente el recurso - "create": POST - solo crea si no existe - "merge" (default): PATCH - updates specific fields - "replace": PUT - completely replaces the resource - "create": POST - only creates if it doesn't exist Returns: bool: True si tuvo éxito bool: True if successful """ conn = _get_connection() sess = conn.start_session() # PUT: Eliminar y recrear (reemplazo completo) # PUT: Delete and recreate (complete replacement) try: if not xpath: sess.delete_item("/ietf-network-slice-service:network-slice-services") Loading @@ -107,25 +107,25 @@ def update_data_store(intent: dict, xpath: str = ""): def patch_data_store(intent: dict, xpath: str = ""): """ UPDATE: Modifica datos en el datastore UPDATE: Modifies data in the datastore Args: intent: Datos a modificar xpath: Ruta al recurso intent: Data to modify xpath: Path to resource operation: - "merge" (default): PATCH - actualiza campos específicos - "replace": PUT - reemplaza completamente el recurso - "create": POST - solo crea si no existe - "merge" (default): PATCH - updates specific fields - "replace": PUT - completely replaces the resource - "create": POST - only creates if it doesn't exist Returns: bool: True si tuvo éxito bool: True if successful """ conn = _get_connection() sess = conn.start_session() # PUT: Eliminar y recrear (reemplazo completo) # PUT: Delete and recreate (complete replacement) try: # PATCH: Merge con datos existentes # PATCH: Merge with existing data _write_dict(sess, xpath, intent) sess.apply_changes() logging.debug(f"Merged data at {xpath}") Loading @@ -141,7 +141,7 @@ def patch_data_store(intent: dict, xpath: str = ""): def delete_data_store(xpath: str = ""): """ DELETE: Elimina datos del datastore DELETE: Deletes data from the datastore """ conn = _get_connection() sess = conn.start_session() Loading @@ -161,8 +161,8 @@ def delete_data_store(xpath: str = ""): def _write_dict(sess, base_xpath, data, parent_key=None): """ Convierte dict → XPaths YANG parent_key: clave que ya está en el xpath y debe ser excluida Converts dict → YANG XPaths parent_key: key already in xpath and should be excluded """ LIST_KEYS = { 'slo-sle-template': 'id', Loading Loading @@ -201,13 +201,13 @@ def _write_dict(sess, base_xpath, data, parent_key=None): # ----------------------------- if isinstance(data, dict): # Saltar diccionarios vacíos # Skip empty dictionaries if not data: logging.debug("Skipping empty dict") return for k, v in data.items(): # IMPORTANTE: Saltar la clave si ya está en el predicado # IMPORTANT: Skip the key if already in the predicate if k == parent_key: logging.debug(f"Skipping key field '{k}' (already in predicate)") continue Loading @@ -219,7 +219,7 @@ def _write_dict(sess, base_xpath, data, parent_key=None): # ----------------------------- elif isinstance(data, list): # Saltar listas vacías # Skip empty lists if not data: logging.debug("Skipping empty list") return Loading @@ -233,7 +233,7 @@ def _write_dict(sess, base_xpath, data, parent_key=None): if is_leaf_list: # ----------------------------- # LEAF-LIST: Lista de valores simples # LEAF-LIST: List of simple values # ----------------------------- logging.debug(f"Processing as LEAF-LIST: {list_name}") Loading @@ -243,13 +243,13 @@ def _write_dict(sess, base_xpath, data, parent_key=None): continue logging.debug(f"Adding leaf-list value: {value}") # En sysrepo, los leaf-lists se agregan con el mismo xpath # pero múltiples valores # In sysrepo, leaf-lists are added with the same xpath # but multiple values sess.set_item(base_xpath, str(value)) else: # ----------------------------- # LIST: Lista de containers # LIST: List of containers # ----------------------------- logging.debug(f"Processing as LIST: {list_name}") key_field = LIST_KEYS.get(list_name) Loading @@ -260,7 +260,7 @@ def _write_dict(sess, base_xpath, data, parent_key=None): item_xpath = f"{base_xpath}[{key_field}='{key_value}']" logging.debug(f"Using key '{key_field}={key_value}' for list '{list_name}'") # Pasar el key_field para que sea excluido al procesar el item # Pass the key_field to be excluded when processing the item _write_dict(sess, item_xpath, item, parent_key=key_field) else: logging.error(f"ERROR: No key '{key_field}' found in item for list '{list_name}'") Loading Loading @@ -292,18 +292,18 @@ def normalize_libyang_data(data): KeyedList = None if KeyedList and isinstance(data, KeyedList): # KeyedList -> lista normal # KeyedList -> normal list return [normalize_libyang_data(item) for item in data] elif isinstance(data, dict): # Dict -> procesar valores recursivamente # Dict -> process values recursively return { key: normalize_libyang_data(value) for key, value in data.items() } elif isinstance(data, (list, tuple)): # Lista/tupla -> procesar elementos recursivamente # List/tuple -> process elements recursively return [normalize_libyang_data(item) for item in data] else: Loading src/mapper/extract_sdp_info.py +2 −2 Original line number Diff line number Diff line Loading @@ -32,12 +32,12 @@ def extract_sdp_info(sdp_id, slice_service, connection_group_id, connectivity_co logging.debug(f"Looking for match criteria with target-connectivity-construct-id: {connectivity_construct_id}") selected_match_criteria = next((mc for mc in match_criteria_list if safe_get(mc, ["target-connectivity-construct-id"]) == connectivity_construct_id), None) # Si no, buscar por connection group # If not, search by connection group if not selected_match_criteria and connection_group_id: logging.debug(f"Looking for match criteria with target-connection-group-id: {connection_group_id}") selected_match_criteria = next((mc for mc in match_criteria_list if safe_get(mc, ["target-connection-group-id"]) == connection_group_id), None) # Si no, usar el primero disponible # If not, use the first available if not selected_match_criteria: logging.debug("No specific match criteria found for connectivity construct or connection group. Using the first available match criteria.") selected_match_criteria = match_criteria_list[0] Loading src/mapper/process_connnectivity.py +0 −1 Original line number Diff line number Diff line Loading @@ -16,7 +16,6 @@ from src.utils.safe_get import safe_get from .extract_sdp_info import extract_sdp_info import logging def process_connectivity(connection_group_id, connectivity_type, connectivity_construct, connectivity_construct_id, slice_service): """ Loading src/mapper/slo_viability.py +1 −1 Original line number Diff line number Diff line Loading @@ -61,4 +61,4 @@ def slo_viability(slice_slos, nrp_slos): # Calculate final viability score score = sum(flexibility_scores) / len(flexibility_scores) if flexibility_scores else 0 return True, score # Si pasó todas las verificaciones, la NRP es viable No newline at end of file return True, score # If it passed all verifications, the NRP is viable No newline at end of file Loading
src/api/main.py +21 −21 Original line number Diff line number Diff line Loading @@ -409,22 +409,22 @@ class Api: def update_network_slice_service(self, intent): """ Modifica (reemplaza) toda la configuración de network-slice-services Modify (replace) all network-slice-services configuration """ try: xpath = "/ietf-network-slice-service:network-slice-services" # Verificar que existe algo que modificar # Verify if there is something to modify existing_data = get_data_store(xpath) if not existing_data: return send_response(False, code=404, message="Network slice services not found") # Si no está en modo DUMMY, procesar con TFS # If not in DUMMY mode, process with TFS result = self.slice_service.nsc(intent) if not result: return send_response(False, code=500, message="Failed to process slice in TFS") # Reemplazar completamente el recurso # Replace completely the resource update_data_store(intent) logging.info("Network slice services modified successfully") Loading @@ -442,17 +442,17 @@ class Api: def update_slo_sle_template(self, template_id, template): """ Modifica (reemplaza) un template SLO/SLE específico Modify (replace) an specific SLO/SLE template """ try: xpath = f"/ietf-network-slice-service:network-slice-services/slo-sle-templates/slo-sle-template[id='{template_id}']" # Verificar que el template existe # Verify the template exists existing_template = get_data_store(xpath) if not existing_template: return send_response(False, code=404, message="Template not found") # Asegurar que el ID en el body coincide con el de la URL # Assure that the body ID matches the URL if "id" in template and template["id"] != template_id: return send_response(False, code=400, message="Template ID in body does not match URL") Loading @@ -474,11 +474,11 @@ class Api: if not result: return send_response(False, code=500, message="Slice not updated") # Eliminar el ID del body si existe (ya está en el predicado) # Remove the ID from the body if it exists (it's already in the predicate) template_data = template.copy() template_data.pop("id", None) # Reemplazar el template # Replace the template update_data_store(template_data, xpath) logging.info(f"Template {template_id} modified successfully") Loading @@ -501,16 +501,16 @@ class Api: try: xpath = f"/ietf-network-slice-service:network-slice-services/slice-service[id='{slice_id}']" # Verificar que el slice existe # Verify that the slice exists existing_slice = get_data_store(xpath) if not existing_slice: return send_response(False, code=404, message="Slice not found") # Asegurar que el ID en el body coincide con el de la URL # Assure that the body ID matches the URL if "id" in intent and intent["id"] != slice_id: return send_response(False, code=400, message="Slice ID in body does not match URL") # Validar que existe el template SLO/SLE referenciado # Validate that the referenced SLO/SLE template exists if "slo-sle-template" in intent: template_ref = intent.get("slo-sle-template") xpath_template = f"/ietf-network-slice-service:network-slice-services/slo-sle-templates/slo-sle-template[id='{template_ref}']" Loading @@ -518,7 +518,7 @@ class Api: if not existing_template: return send_response(False, code=404, message="Referenced SLO/SLE template not found") # Construir el intent completo para TFS # Build the full intent full_intent = { "ietf-network-slice-service:network-slice-services": { "slo-sle-templates": { Loading @@ -544,11 +544,11 @@ class Api: if not result: return send_response(False, code=500, message="Slice not updated") # Eliminar el ID del body (ya está en el predicado) # Remove the ID from the body (it's already in the predicate) intent_data = intent.copy() intent_data.pop("id", None) # Reemplazar el slice # Replace the slice update_data_store(intent_data, xpath) logging.info(f"Slice {slice_id} modified successfully") Loading @@ -568,30 +568,30 @@ class Api: def update_sdp(self, slice_id, sdp_id, sdp): """ Modifica (reemplaza) un SDP específico dentro de un slice Modify (replace) an specific SDP in the slice """ try: # Verificar que el slice existe # Verify the template exists slice_xpath = f"/ietf-network-slice-service:network-slice-services/slice-service[id='{slice_id}']" existing_slice = get_data_store(slice_xpath) if not existing_slice: return send_response(False, code=404, message="Slice not found") # Verificar que el SDP existe # Verify the SDP exists sdp_xpath = f"/ietf-network-slice-service:network-slice-services/slice-service[id='{slice_id}']/sdps/sdp[id='{sdp_id}']" existing_sdp = get_data_store(sdp_xpath) if not existing_sdp: return send_response(False, code=404, message="SDP not found") # Asegurar que el ID en el body coincide con el de la URL # Assure that the body ID matches the URL if "id" in sdp and sdp["id"] != sdp_id: return send_response(False, code=400, message="SDP ID in body does not match URL") # Eliminar el ID del body (ya está en el predicado) # Remove the ID from the body (it's already in the predicate) sdp_data = sdp.copy() sdp_data.pop("id", None) # Reemplazar el SDP # Replace the SDP update_data_store(sdp_data, sdp_xpath) logging.info(f"SDP {sdp_id} in slice {slice_id} modified successfully") Loading
src/database/sysrepo_store.py +42 −42 Original line number Diff line number Diff line Loading @@ -21,13 +21,13 @@ import logging def _get_connection(): """ Crea una nueva conexión cada vez Creates a new connection each time """ return sysrepo.SysrepoConnection() def create_data_store(intent: dict, xpath: str= ""): """ CREATE: Crea nuevos datos en el datastore CREATE: Creates new data in the datastore """ conn = _get_connection() sess = conn.start_session() Loading @@ -48,7 +48,7 @@ def create_data_store(intent: dict, xpath: str= ""): def get_data_store(xpath: str = ""): """ Obtiene todos los slices del datastore Gets all slices from the datastore """ conn = _get_connection() sess = conn.start_session() Loading @@ -69,23 +69,23 @@ def get_data_store(xpath: str = ""): def update_data_store(intent: dict, xpath: str = ""): """ UPDATE: Modifica datos en el datastore UPDATE: Modifies data in the datastore Args: intent: Datos a modificar xpath: Ruta al recurso intent: Data to modify xpath: Path to resource operation: - "merge" (default): PATCH - actualiza campos específicos - "replace": PUT - reemplaza completamente el recurso - "create": POST - solo crea si no existe - "merge" (default): PATCH - updates specific fields - "replace": PUT - completely replaces the resource - "create": POST - only creates if it doesn't exist Returns: bool: True si tuvo éxito bool: True if successful """ conn = _get_connection() sess = conn.start_session() # PUT: Eliminar y recrear (reemplazo completo) # PUT: Delete and recreate (complete replacement) try: if not xpath: sess.delete_item("/ietf-network-slice-service:network-slice-services") Loading @@ -107,25 +107,25 @@ def update_data_store(intent: dict, xpath: str = ""): def patch_data_store(intent: dict, xpath: str = ""): """ UPDATE: Modifica datos en el datastore UPDATE: Modifies data in the datastore Args: intent: Datos a modificar xpath: Ruta al recurso intent: Data to modify xpath: Path to resource operation: - "merge" (default): PATCH - actualiza campos específicos - "replace": PUT - reemplaza completamente el recurso - "create": POST - solo crea si no existe - "merge" (default): PATCH - updates specific fields - "replace": PUT - completely replaces the resource - "create": POST - only creates if it doesn't exist Returns: bool: True si tuvo éxito bool: True if successful """ conn = _get_connection() sess = conn.start_session() # PUT: Eliminar y recrear (reemplazo completo) # PUT: Delete and recreate (complete replacement) try: # PATCH: Merge con datos existentes # PATCH: Merge with existing data _write_dict(sess, xpath, intent) sess.apply_changes() logging.debug(f"Merged data at {xpath}") Loading @@ -141,7 +141,7 @@ def patch_data_store(intent: dict, xpath: str = ""): def delete_data_store(xpath: str = ""): """ DELETE: Elimina datos del datastore DELETE: Deletes data from the datastore """ conn = _get_connection() sess = conn.start_session() Loading @@ -161,8 +161,8 @@ def delete_data_store(xpath: str = ""): def _write_dict(sess, base_xpath, data, parent_key=None): """ Convierte dict → XPaths YANG parent_key: clave que ya está en el xpath y debe ser excluida Converts dict → YANG XPaths parent_key: key already in xpath and should be excluded """ LIST_KEYS = { 'slo-sle-template': 'id', Loading Loading @@ -201,13 +201,13 @@ def _write_dict(sess, base_xpath, data, parent_key=None): # ----------------------------- if isinstance(data, dict): # Saltar diccionarios vacíos # Skip empty dictionaries if not data: logging.debug("Skipping empty dict") return for k, v in data.items(): # IMPORTANTE: Saltar la clave si ya está en el predicado # IMPORTANT: Skip the key if already in the predicate if k == parent_key: logging.debug(f"Skipping key field '{k}' (already in predicate)") continue Loading @@ -219,7 +219,7 @@ def _write_dict(sess, base_xpath, data, parent_key=None): # ----------------------------- elif isinstance(data, list): # Saltar listas vacías # Skip empty lists if not data: logging.debug("Skipping empty list") return Loading @@ -233,7 +233,7 @@ def _write_dict(sess, base_xpath, data, parent_key=None): if is_leaf_list: # ----------------------------- # LEAF-LIST: Lista de valores simples # LEAF-LIST: List of simple values # ----------------------------- logging.debug(f"Processing as LEAF-LIST: {list_name}") Loading @@ -243,13 +243,13 @@ def _write_dict(sess, base_xpath, data, parent_key=None): continue logging.debug(f"Adding leaf-list value: {value}") # En sysrepo, los leaf-lists se agregan con el mismo xpath # pero múltiples valores # In sysrepo, leaf-lists are added with the same xpath # but multiple values sess.set_item(base_xpath, str(value)) else: # ----------------------------- # LIST: Lista de containers # LIST: List of containers # ----------------------------- logging.debug(f"Processing as LIST: {list_name}") key_field = LIST_KEYS.get(list_name) Loading @@ -260,7 +260,7 @@ def _write_dict(sess, base_xpath, data, parent_key=None): item_xpath = f"{base_xpath}[{key_field}='{key_value}']" logging.debug(f"Using key '{key_field}={key_value}' for list '{list_name}'") # Pasar el key_field para que sea excluido al procesar el item # Pass the key_field to be excluded when processing the item _write_dict(sess, item_xpath, item, parent_key=key_field) else: logging.error(f"ERROR: No key '{key_field}' found in item for list '{list_name}'") Loading Loading @@ -292,18 +292,18 @@ def normalize_libyang_data(data): KeyedList = None if KeyedList and isinstance(data, KeyedList): # KeyedList -> lista normal # KeyedList -> normal list return [normalize_libyang_data(item) for item in data] elif isinstance(data, dict): # Dict -> procesar valores recursivamente # Dict -> process values recursively return { key: normalize_libyang_data(value) for key, value in data.items() } elif isinstance(data, (list, tuple)): # Lista/tupla -> procesar elementos recursivamente # List/tuple -> process elements recursively return [normalize_libyang_data(item) for item in data] else: Loading
src/mapper/extract_sdp_info.py +2 −2 Original line number Diff line number Diff line Loading @@ -32,12 +32,12 @@ def extract_sdp_info(sdp_id, slice_service, connection_group_id, connectivity_co logging.debug(f"Looking for match criteria with target-connectivity-construct-id: {connectivity_construct_id}") selected_match_criteria = next((mc for mc in match_criteria_list if safe_get(mc, ["target-connectivity-construct-id"]) == connectivity_construct_id), None) # Si no, buscar por connection group # If not, search by connection group if not selected_match_criteria and connection_group_id: logging.debug(f"Looking for match criteria with target-connection-group-id: {connection_group_id}") selected_match_criteria = next((mc for mc in match_criteria_list if safe_get(mc, ["target-connection-group-id"]) == connection_group_id), None) # Si no, usar el primero disponible # If not, use the first available if not selected_match_criteria: logging.debug("No specific match criteria found for connectivity construct or connection group. Using the first available match criteria.") selected_match_criteria = match_criteria_list[0] Loading
src/mapper/process_connnectivity.py +0 −1 Original line number Diff line number Diff line Loading @@ -16,7 +16,6 @@ from src.utils.safe_get import safe_get from .extract_sdp_info import extract_sdp_info import logging def process_connectivity(connection_group_id, connectivity_type, connectivity_construct, connectivity_construct_id, slice_service): """ Loading
src/mapper/slo_viability.py +1 −1 Original line number Diff line number Diff line Loading @@ -61,4 +61,4 @@ def slo_viability(slice_slos, nrp_slos): # Calculate final viability score score = sum(flexibility_scores) / len(flexibility_scores) if flexibility_scores else 0 return True, score # Si pasó todas las verificaciones, la NRP es viable No newline at end of file return True, score # If it passed all verifications, the NRP is viable No newline at end of file