Esempio n. 1
0
    def _get_scale_value(self, scale: str):
        try:
            value = float(scale)
        except ValueError:
            ast = string_to_ast(expression_with_parameters, scale)

            evaluation_issues: List[Tuple[int, str]] = []
            s = State()
            value, unresolved_vars = ast_evaluator(exp=ast,
                                                   state=s,
                                                   obj=None,
                                                   issue_lst=evaluation_issues)

            if len(evaluation_issues) > 0:
                evaluation_issues_str = [i[1] for i in evaluation_issues]
                raise CommandExecutionError(
                    f"Problems evaluating scale expression '{scale}': "
                    f"{', '.join(evaluation_issues_str)}")
            elif len(unresolved_vars) > 0:
                raise CommandExecutionError(
                    f"Unresolved variables evaluating the scale expression '{scale}':"
                    f" {', '.join(unresolved_vars)}")

            elif not value:
                raise CommandExecutionError(
                    f"The scale expression '{scale}' could not be evaluated.")

        return value
Esempio n. 2
0
 def check_existence_of_pedigree_matrix(self,
                                        pedigree_matrix: str,
                                        pedigree: str,
                                        subrow=None) -> NoReturn:
     # Check existence of PedigreeMatrix, if used
     if pedigree_matrix and pedigree:
         pm = self._glb_idx.get(
             PedigreeMatrix.partial_key(name=pedigree_matrix))
         if len(pm) == 0:
             raise CommandExecutionError(
                 "Could not find Pedigree Matrix '" + pedigree_matrix +
                 "'" + subrow_issue_message(subrow))
         else:
             try:
                 lst = pm[0].get_modes_for_code(pedigree)
             except:
                 raise CommandExecutionError("Could not decode Pedigree '" +
                                             pedigree +
                                             "' for Pedigree Matrix '" +
                                             pedigree_matrix + "'" +
                                             subrow_issue_message(subrow))
     elif pedigree and not pedigree_matrix:
         raise CommandExecutionError(
             "Pedigree specified without accompanying Pedigree Matrix" +
             subrow_issue_message(subrow))
    def _check_fields(self,
                      relation_class: RelationClassType,
                      source_processor: Processor,
                      target_processor: Processor,
                      subrow=None):
        # Use of column BackInterface is only allowed in some relation types
        back_allowed_classes = [
            RelationClassType.ff_directed_flow,
            RelationClassType.ff_reverse_directed_flow,
            RelationClassType.ff_directed_flow_back
        ]
        if self._fields[
                "back_interface"] and relation_class not in back_allowed_classes:
            raise CommandExecutionError(
                f"Column 'BackInterface' is only allowed in relations of type: "
                f"{back_allowed_classes}" + subrow_issue_message(subrow))

        # Use of column Weight is only allowed in some relation types
        weight_allowed_classes = [
            RelationClassType.ff_directed_flow,
            RelationClassType.ff_reverse_directed_flow,
            RelationClassType.ff_directed_flow_back, RelationClassType.ff_scale
        ]
        if self._fields[
                "flow_weight"] and relation_class not in weight_allowed_classes:
            raise CommandExecutionError(
                f"Column 'Weight' is only allowed in relations of type: "
                f"{weight_allowed_classes}")

        # Processors should be the same when relation is "Scale Change"
        if relation_class == RelationClassType.ff_scale_change and source_processor.name != target_processor.name:
            raise CommandExecutionError(
                f"Source and target processors should be the same for a relation of type"
                f" 'Scale Change': '{source_processor.name}' != '{target_processor.name}'"
            )
    def _check_flow_back_interface_types(self, source: Factor, target: Factor,
                                         back: Factor):
        if source.taxon == target.taxon:
            raise CommandExecutionError(
                f"The type of source and target interfaces should be different. "
                f"Source and Target: {source.taxon.name}")

        if back.taxon != target.taxon:
            raise CommandExecutionError(
                f"The type of target and back interfaces should be the same. Target: "
                f"{target.taxon.name}; Back: {back.taxon.name}")
Esempio n. 5
0
    def get_source(self, reference_name, subrow) -> Any:
        reference = None

        if reference_name:
            try:
                ast = parser_field_parsers.string_to_ast(
                    parser_field_parsers.reference, reference_name)
                ref_id = ast["ref_id"]
                references = self._glb_idx.get(
                    ProvenanceReference.partial_key(ref_id))
                if len(references) == 1:
                    reference = references[0]
                else:
                    references = self._glb_idx.get(
                        BibliographicReference.partial_key(ref_id))
                    if len(references) == 1:
                        reference = references[0]
                    else:
                        raise CommandExecutionError(
                            f"Reference '{reference_name}' not found" +
                            subrow_issue_message(subrow))
            except:
                # TODO Change when Ref* are implemented
                reference = reference_name + " (not found)"

        return reference
Esempio n. 6
0
    def find_processor(self, processor_name, subrow) -> Processor:
        # Find Processor
        # TODO Allow creating a basic Processor if it is not found?
        processors = find_processors_matching_name(processor_name,
                                                   self._glb_idx)
        # p = find_observable_by_name(processor_name, self._glb_idx)
        # p = self._glb_idx.get(Processor.partial_key(processor_name))
        if len(processors) == 0:
            raise CommandExecutionError("Processor '" + processor_name +
                                        "' not declared previously" +
                                        subrow_issue_message(subrow))
        elif len(processors) > 1:
            raise CommandExecutionError(
                f"Processor '{processor_name}' declared previously {len(processors)} times"
                + subrow_issue_message(subrow))

        return processors[0]
Esempio n. 7
0
    def transform_text_attributes_into_dictionary(self, text_attributes: str,
                                                  subrow) -> Dict:
        dictionary_attributes = {}
        if text_attributes:
            try:
                dictionary_attributes = dictionary_from_key_value_list(
                    text_attributes, self._glb_idx)
            except Exception as e:
                raise CommandExecutionError(
                    str(e) + subrow_issue_message(subrow))

        return dictionary_attributes
    def _check_flow_orientation(self, source_processor: Processor,
                                target_processor: Processor,
                                source_interface: Factor,
                                target_interface: Factor,
                                is_direct_flow: bool):
        """Check for correct interfaces orientation (input/output) of source and target"""
        allowed_source_orientation = ("Output" if is_direct_flow else "Input")

        # Are the orientations equal?
        if strcmp(source_interface.orientation, target_interface.orientation):
            if strcmp(source_interface.orientation,
                      allowed_source_orientation):
                # Target processor should be parent of source processor
                parent_processor, child_processor = target_processor, source_processor
            else:
                # Source processor should be parent of target processor
                parent_processor, child_processor = source_processor, target_processor

            if child_processor not in parent_processor.children(self._glb_idx):
                raise CommandExecutionError(
                    f"The processor '{child_processor.name}' should be part of the "
                    f"processor '{parent_processor.name}' when using the same interface "
                    f"orientation '{source_interface.orientation}'.")

        else:  # Orientations are different
            if not strcmp(source_interface.orientation,
                          allowed_source_orientation):
                raise CommandExecutionError(
                    f"The source interface '{source_interface.full_name}' has the wrong "
                    f"orientation '{source_interface.orientation}'.")

            if strcmp(target_interface.orientation,
                      allowed_source_orientation):
                raise CommandExecutionError(
                    f"The target interface '{target_interface.full_name}' has the wrong "
                    f"orientation '{target_interface.orientation}'.")
Esempio n. 9
0
    def get_location(self, reference_name, subrow) -> Any:
        reference = None

        if reference_name:
            try:
                # TODO Change to parser for Location (includes references, but also Codes)
                ast = parser_field_parsers.string_to_ast(
                    parser_field_parsers.reference, reference_name)
                ref_id = ast["ref_id"]
                references = self._glb_idx.get(
                    GeographicReference.partial_key(ref_id))
                if len(references) == 1:
                    reference = references[0]
                else:
                    raise CommandExecutionError(
                        f"Reference '{reference_name}' not found" +
                        subrow_issue_message(subrow))
            except:
                reference = reference_name

        return reference
Esempio n. 10
0
    def _process_row(self, fields: Dict[str, Any], subrow=None) -> None:
        scaling_type = fields["scaling_type"]
        scale: str = fields["scale"]

        # Find processors
        invoking_processor = self._get_processor_from_field(
            "invoking_processor")
        requested_processor = self._get_processor_from_field(
            "requested_processor")

        if invoking_processor == requested_processor:
            raise CommandExecutionError(
                f"Invoking and Requested processors cannot be the same '{invoking_processor.name}'. "
                f"Use the 'relative_to' attribute in 'Interfaces' command instead."
            )

        invoking_interface_name: str = fields["invoking_interface"]
        requested_interface_name: str = fields["requested_interface"]

        requested_new_processor_name: str = fields["new_processor_name"]

        ##
        # Transform text of "attributes" into a dictionary
        if fields.get("attributes"):
            try:
                fields["attributes"] = dictionary_from_key_value_list(
                    fields["attributes"], self._glb_idx)
            except Exception as e:
                self._add_issue(IType.ERROR,
                                str(e) + subrow_issue_message(subrow))
                return
        else:
            fields["attributes"] = {}

        # Process specific fields

        # Obtain the parent: it must exist. It could be created dynamically but it's important to specify attributes
        if fields.get("parent_processor"):
            try:
                parent_processor = self._get_processor_from_field(
                    "parent_processor")
            except CommandExecutionError:
                self._add_issue(
                    IType.ERROR,
                    f"Specified parent processor, '{fields.get('parent_processor')}', does not exist"
                    + subrow_issue_message(subrow))
                return
        else:
            parent_processor = None

        # Get internal and user-defined attributes in one dictionary
        attributes = {
            c.name: fields[c.name]
            for c in self._command_fields
            if c.attribute_of == Processor and fields[c.name] is not None
        }

        # print(f"Invoking: {invoking_processor.name}:{invoking_interface_name}, Requested: {requested_processor.name}:{requested_interface_name}")

        requested_processor_clone = None
        if strcmp(scaling_type, "CloneAndScale") or strcmp(
                scaling_type, "Clone"):
            # TODO: check “RequestedProcessor” must be an archetype
            # 1. Clones “RequestedProcessor” as a child of “InvokingProcessor”
            requested_processor_clone = self._clone_processor_as_child(
                processor=requested_processor,
                parent_processor=invoking_processor
                if not parent_processor else parent_processor,
                name=requested_new_processor_name,
                other_attributes=attributes)

            if strcmp(scaling_type, "CloneAndScale"):
                # 2. Constrains the value of “RequestedInterface” to the value of “InvokingInterface”, scaled by “Scale”
                try:
                    self._constrains_interface(
                        scale=scale,
                        invoking_interface_name=invoking_interface_name,
                        requested_interface_name=requested_interface_name,
                        parent_processor=invoking_processor,
                        child_processor=requested_processor_clone)
                except Exception as e:
                    self._add_issue(IType.ERROR,
                                    str(e) + subrow_issue_message(subrow))
                    return
        elif strcmp(scaling_type, "Scale"):
            # Processors must be of same type (archetype or instance)
            if not strcmp(invoking_processor.instance_or_archetype,
                          requested_processor.instance_or_archetype):
                raise CommandExecutionError(
                    "Requested and invoking processors should be of the same type "
                    "(both instance or_archetype)")

            # 1. Constrains the value of “RequestedInterface” to the value of “InvokingInterface”, scaled by “Scale”
            try:
                self._constrains_interface(
                    scale=scale,
                    invoking_interface_name=invoking_interface_name,
                    requested_interface_name=requested_interface_name,
                    parent_processor=invoking_processor,
                    child_processor=requested_processor)
            except Exception as e:
                self._add_issue(IType.ERROR,
                                str(e) + subrow_issue_message(subrow))
                return

        elif strcmp(scaling_type, "CloneScaled"):
            # “RequestedProcessor” must be an archetype
            # if not strcmp(requested_processor.instance_or_archetype, "archetype"):
            #     raise CommandExecutionError(f"Requested processor '{requested_processor.name}' should be of type 'archetype'")

            # “InvokingProcessor” must be an instance
            # if not strcmp(invoking_processor.instance_or_archetype, "instance"):
            #     raise CommandExecutionError(f"Invoking processor '{invoking_processor.name}' should be of type 'instance'")

            # 1. Clones “RequestedProcessor” as a child of “InvokingProcessor”
            # 2. Scales the new processor using “Scale” as the value of “RequestedInterface”
            requested_processor_clone = self._clone_processor_as_child(
                processor=requested_processor,
                parent_processor=invoking_processor
                if not parent_processor else parent_processor,
                other_attributes=attributes)

            # Value Scale, which can be an expression, should be evaluated (ast) because we need a final float number
            scale_value = self._get_scale_value(scale)

            # In the cloned processor search in all interfaces if there are Observations relative_to RequestedInterface
            # and multiply the observation by the computed scale.
            self._scale_observations_relative_to_interface(
                processor=requested_processor_clone,
                interface_name=requested_interface_name,
                scale=scale_value)

        if requested_processor_clone:
            # Find or create processor and REGISTER it in "glb_idx"
            # Add to ProcessorsGroup, if specified
            field_val = fields.get("processor_group")
            if field_val:
                p_set = self._p_sets.get(field_val, ProcessorsSet(field_val))
                self._p_sets[field_val] = p_set
                if p_set.append(
                        requested_processor_clone, self._glb_idx
                ):  # Appends codes to the pset if the processor was not member of the pset
                    p_set.append_attributes_codes(fields["attributes"])
Esempio n. 11
0
    def _process_row(self, field_values: Dict[str, Any], subrow=None) -> None:
        """
        Process a dictionary representing a row of the Interfaces command. The dictionary can come directly from
        the worksheet or from a dataset.

        :param field_values: dictionary
        """
        # f_processor_name -> p
        # f_interface_type_name -> it
        # f_interface_name -> i
        #
        # IF NOT i AND it AND p => i_name = it.name => get or create "i"
        # IF i AND it AND p => get or create "i", IF "i" exists, i.it MUST BE equal to "it" (IF NOT, error)
        # IF i AND p AND NOT it => get "i" (MUST EXIST)
        f_interface_type_name = field_values.get("interface_type")
        f_interface_name = field_values.get("interface")

        if not f_interface_name:
            if not f_interface_type_name:
                raise CommandExecutionError(
                    "At least one of InterfaceType or Interface must be defined"
                    + subrow_issue_message(subrow))

            f_interface_name = f_interface_type_name

        processor = self.find_processor(field_values.get("processor"), subrow)

        # Try to find Interface
        f_orientation = field_values.get("orientation")
        interface_type: Optional[FactorType] = None
        interface: Optional[Factor] = None
        interfaces: Sequence[Factor] = self._glb_idx.get(
            Factor.partial_key(processor=processor, name=f_interface_name))
        if len(interfaces) == 1:
            interface = interfaces[0]
            print(f"DEBUG - Interface '{interface.name}' found")
            interface_type = interface.taxon
            if f_interface_type_name and not strcmp(interface_type.name,
                                                    f_interface_type_name):
                self._add_issue(
                    IType.WARNING,
                    f"The existing Interface '{interface.name}' has the InterfaceType "
                    f"'{interface_type.name}' which is different from the specified "
                    f"InterfaceType '{f_interface_type_name}'. Record skipped."
                    + subrow_issue_message(subrow))
                return
        elif len(interfaces) > 1:
            raise CommandExecutionError(
                f"Interface '{f_interface_name}' found {str(len(interfaces))} times. "
                f"It must be uniquely identified." +
                subrow_issue_message(subrow))
        elif len(interfaces) == 0:
            # The interface does not exist, create it below
            if not f_orientation:
                raise CommandExecutionError(
                    f"Orientation must be defined for new Interfaces." +
                    subrow_issue_message(subrow))

        # InterfaceType still not found
        if not interface_type:
            interface_type_name = ifnull(f_interface_type_name,
                                         f_interface_name)

            # Find FactorType
            # TODO Allow creating a basic FactorType if it is not found?
            interface_types: Sequence[FactorType] = self._glb_idx.get(
                FactorType.partial_key(interface_type_name))
            if len(interface_types) == 0:
                raise CommandExecutionError(
                    f"InterfaceType '{interface_type_name}' not declared previously"
                    + subrow_issue_message(subrow))
            elif len(interface_types) > 1:
                raise CommandExecutionError(
                    f"InterfaceType '{interface_type_name}' found {str(len(interface_types))} times. "
                    f"It must be uniquely identified." +
                    subrow_issue_message(subrow))
            else:
                interface_type = interface_types[0]

        # Get attributes default values taken from Interface Type or Processor attributes
        # Rows   : value of (source) "processor.subsystem_type"
        # Columns: value of (target) "interface_type.opposite_processor_type"
        # Cells  : CORRECTED value of "opposite_processor_type"
        # +--------+-------+--------+-------+---------+
        # |        | Local | Env    | Ext   | ExtEnv  |
        # +--------+-------+--------+-------+---------+
        # | Local  | Local | Env    | Ext   | ExtEnv  |
        # | Env    | Local | Env    | Ext   | ExtEnv? |
        # | Ext    | Ext   | ExtEnv | Local | Env     |
        # | ExtEnv | Ext   | ExtEnv | Local | Env?    |
        # +--------+-------+--------+-------+---------+
        if interface_type.opposite_processor_type:
            tmp = interface_type.opposite_processor_type.lower()
            if processor.subsystem_type.lower() in ["local", "environment"
                                                    ]:  # First two rows
                opposite_processor_type = tmp
            else:
                opposite_processor_type = InterfacesAndQualifiedQuantitiesCommand.invert[
                    tmp]
            # TODO in doubt. Maybe these are undefined (values with question mark in the table)
            #  if tmp == "externalenvironment" and processor.subsystem_type.lower() in ["environment", "externalenvironment"]:
            #      pass
        else:
            opposite_processor_type = None

        interface_type_values = {
            "sphere": interface_type.sphere,
            "roegen_type": interface_type.roegen_type,
            "opposite_processor_type": opposite_processor_type
        }

        # Get internal and user-defined attributes in one dictionary
        # Use: value specified in Interfaces ELSE value specified in InterfaceTypes ELSE first value of allowed values
        attributes = {
            c.name: ifnull(
                field_values[c.name],
                ifnull(interface_type_values.get(c.name),
                       head(c.allowed_values)))
            for c in self._command_fields if c.attribute_of == Factor
        }

        if not interface:
            # f_list: Sequence[Factor] = self._glb_idx.get(
            #     Factor.partial_key(processor=p, factor_type=ft, orientation=f_orientation))
            #
            # if len(f_list) > 0:
            #     raise CommandExecutionError(f"An interface called '{f_list[0].name}' for Processor '{f_processor_name}'"
            #                                  f" with InterfaceType '{f_interface_type_name}' and orientation "
            #                                  f"'{f_orientation}' already exists"+subrow_issue_message(subrow))

            # Transform text of "interface_attributes" into a dictionary
            interface_attributes = self.transform_text_attributes_into_dictionary(
                field_values.get("interface_attributes"), subrow)
            attributes.update(interface_attributes)

            location = self.get_location(field_values.get("location"), subrow)

            interface = Factor.create_and_append(
                f_interface_name,
                processor,
                in_processor_type=FactorInProcessorType(external=False,
                                                        incoming=False),
                taxon=interface_type,
                geolocation=location,
                tags=None,
                attributes=attributes)
            self._glb_idx.put(interface.key(), interface)
            print(f"DEBUG - Interface '{interface.name}' created")
        elif not interface.compare_attributes(attributes):
            initial = ', '.join(
                [f"{k}: {interface.get_attribute(k)}" for k in attributes])
            new = ', '.join([f"{k}: {attributes[k]}" for k in attributes])
            name = interface.processor.full_hierarchy_names(
                self._glb_idx)[0] + ":" + interface.name
            raise CommandExecutionError(
                f"The same interface '{name}', is being redeclared with different properties. "
                f"INITIAL: {initial}; NEW: {new}." +
                subrow_issue_message(subrow))

        f_unit = field_values.get("unit")
        if not f_unit:
            f_unit = interface_type.unit

        # Unify unit (it must be done before considering RelativeTo -below-, because it adds a transformation to "f_unit")
        f_value = field_values.get("value")
        if f_value is not None and f_unit != interface_type.unit:
            try:
                f_value = UnitConversion.convert(f_value, f_unit,
                                                 interface_type.unit)
            except DimensionalityError:
                raise CommandExecutionError(
                    f"Dimensions of units in InterfaceType ({interface_type.unit}) and specified ({f_unit}) are not convertible"
                    + subrow_issue_message(subrow))

            f_unit = interface_type.unit

        # Search for a relative_to interface
        f_relative_to = field_values.get("relative_to")
        relative_to_interface: Optional[Factor] = None
        if f_relative_to:
            try:
                ast = parser_field_parsers.string_to_ast(
                    parser_field_parsers.factor_unit, f_relative_to)
            except:
                raise CommandExecutionError(
                    f"Could not parse the RelativeTo column, value {str(f_relative_to)}. "
                    + subrow_issue_message(subrow))

            relative_to_interface_name = ast_to_string(ast["factor"])

            # rel_unit_name = ast["unparsed_unit"]
            # try:
            #     f_unit = str((ureg(f_unit) / ureg(rel_unit_name)).units)
            # except (UndefinedUnitError, AttributeError) as ex:
            #     raise CommandExecutionError(f"The final unit could not be computed, interface '{f_unit}' / "
            #                                  f"relative_to '{rel_unit_name}': {str(ex)}"+subrow_issue_message(subrow))

            relative_to_interface = first(
                interface.processor.factors,
                lambda ifc: strcmp(ifc.name, relative_to_interface_name))

            if not relative_to_interface:
                raise CommandExecutionError(
                    f"Interface specified in 'relative_to' column "
                    f"'{relative_to_interface_name}' has not been found." +
                    subrow_issue_message(subrow))

        if f_value is None and relative_to_interface is not None:
            # Search for a Interface Type Conversion defined in the ScaleChangeMap command
            interface_types_transforms: List[FactorTypesRelationUnidirectionalLinearTransformObservation] = \
                find_factor_types_transform_relation(self._glb_idx, relative_to_interface.taxon, interface.taxon, processor, processor)

            # Overwrite any specified unit, it doesn't make sense without a value, i.e. it cannot be used for conversion
            f_unit = interface.taxon.unit
            if len(interface_types_transforms) == 1:
                f_value = interface_types_transforms[0].scaled_weight
            else:
                interface_types_transforms_message = "an interface type conversion doesn't exist" \
                    if (len(interface_types_transforms) == 0) \
                    else f"{len(interface_types_transforms)} interface type conversions exist"

                f_value = "0"
                self._add_issue(
                    IType.WARNING,
                    f"Field 'value' should be defined for interfaces having a "
                    f"'RelativeTo' interface, and {interface_types_transforms_message}. "
                    f"Using value '0'." + subrow_issue_message(subrow))

        # Create quantitative observation
        if f_value is not None:
            f_uncertainty = field_values.get("uncertainty")
            f_assessment = field_values.get("assessment")
            f_pedigree_matrix = field_values.get("pedigree_matrix")
            f_pedigree = field_values.get("pedigree")
            f_time = field_values.get("time")
            f_comments = field_values.get("comments")

            f_source = field_values.get("qq_source")
            # TODO: source is not being used
            source = self.get_source(f_source, subrow)

            # Find Observer
            observer: Optional[Observer] = None
            if f_source:
                observer = self._glb_idx.get_one(
                    Observer.partial_key(f_source))
                if not observer:
                    self._add_issue(
                        IType.WARNING,
                        f"Observer '{f_source}' has not been found." +
                        subrow_issue_message(subrow))

            # If an observation exists then "time" is mandatory
            if not f_time:
                raise CommandExecutionError(
                    f"Field 'time' needs to be specified for the given observation."
                    + subrow_issue_message(subrow))

            # An interface can have multiple observations if each of them have a different [time, observer] combination
            for observation in interface.quantitative_observations:
                observer_name = observation.observer.name if observation.observer else None
                if strcmp(observation.attributes["time"], f_time) and strcmp(
                        observer_name, f_source):
                    raise CommandExecutionError(
                        f"The interface '{interface.name}' in processor '{interface.processor.name}' already has an "
                        f"observation with time '{f_time}' and source '{f_source}'."
                    )

            self.check_existence_of_pedigree_matrix(f_pedigree_matrix,
                                                    f_pedigree, subrow)

            # Transform text of "number_attributes" into a dictionary
            number_attributes = self.transform_text_attributes_into_dictionary(
                field_values.get("number_attributes"), subrow)

            o = _create_or_append_quantitative_observation(
                interface, f_value, f_unit, f_uncertainty, f_assessment,
                f_pedigree, f_pedigree_matrix, observer, relative_to_interface,
                f_time, None, f_comments, None, number_attributes)
    def _process_row(self, fields: Dict[str, Any], subrow=None) -> None:
        def process_relation(relation_class):
            source_processor = self._get_processor_from_field(
                "source_processor")
            target_processor = self._get_processor_from_field(
                "target_processor")

            self._check_fields(relation_class, source_processor,
                               target_processor, subrow)

            if relation_class.is_between_processors:
                create_relation_observations(
                    self._glb_idx,
                    source_processor, [(target_processor, relation_class)],
                    relation_class,
                    None,
                    attributes=attributes)

            elif relation_class.is_between_interfaces:
                try:
                    source_interface = self._get_interface_from_field(
                        "source_interface",
                        source_processor) if self._fields.get(
                            "source_interface"
                        ) else self._get_interface_from_field(
                            "target_interface", source_processor)
                except CommandExecutionError as e:
                    source_interface = None
                    if not str(e).startswith("The interface"):
                        raise e
                    else:
                        self._add_issue(IType.WARNING, str(e))

                try:
                    target_interface = self._get_interface_from_field(
                        "target_interface",
                        target_processor) if self._fields.get(
                            "target_interface"
                        ) else self._get_interface_from_field(
                            "source_interface", target_processor)
                except CommandExecutionError as e:
                    target_interface = None
                    if not str(e).startswith("The interface"):
                        raise e
                    else:
                        self._add_issue(IType.WARNING, str(e))

                if not source_interface or not target_interface:
                    return

                if fields["back_interface"]:
                    relation_class = RelationClassType.ff_directed_flow_back

                if relation_class == RelationClassType.ff_directed_flow_back:
                    back_interface = self._get_interface_from_field(
                        "back_interface", source_processor)
                    self._check_flow_back_interface_types(
                        source_interface, target_interface, back_interface)
                    attributes.update(dict(back_interface=back_interface))

                if relation_class.is_flow:
                    self._check_flow_orientation(
                        source_processor,
                        target_processor,
                        source_interface,
                        target_interface,
                        is_direct_flow=(relation_class ==
                                        RelationClassType.ff_directed_flow))

                if source_interface.taxon != target_interface.taxon:
                    interface_types_transforms = find_factor_types_transform_relation(
                        self._glb_idx, source_interface.taxon,
                        target_interface.taxon, source_processor,
                        target_processor)

                    # ChangeOfTypeScale
                    if self._fields.get("change_type_scale"):
                        o = FactorTypesRelationUnidirectionalLinearTransformObservation.create_and_append(
                            source_interface.taxon,
                            target_interface.taxon,
                            self._fields.get("change_type_scale"),
                            source_interface.processor,
                            target_interface.
                            processor,  # AdHoc source-target Context
                            None,
                            None,  # No unit conversion
                            find_or_create_observer(
                                Observer.no_observer_specified, self._glb_idx))
                        self._glb_idx.put(o.key(), o)
                        if len(interface_types_transforms) > 0:
                            self._add_issue(
                                IType.WARNING,
                                f"Preexisting matching ScaleChangeMap entry found. Overriding with "
                                f"{self._fields.get('change_type_scale')}")

                    interface_types_transform = self._get_interface_types_transform(
                        source_interface.taxon, source_processor,
                        target_interface.taxon, target_processor, subrow)
                    attributes.update(
                        dict(scale_change_weight=interface_types_transform.
                             scaled_weight))

                create_relation_observations(
                    self._glb_idx,
                    source_interface,
                    [(target_interface, relation_class, fields["flow_weight"])
                     ],
                    relation_class,
                    None,
                    attributes=attributes)

        if not self._all_processors:
            self._all_processors = get_processor_names_to_processors_dictionary(
                self._glb_idx)
        # source_cardinality = fields["source_cardinality"]
        # target_cardinality = fields["target_cardinality"]
        source_processors = self._fields["source_processor"]
        target_processors = self._fields["target_processor"]
        attributes = self._get_attributes_from_field("attributes")

        try:  # Get relation class type
            relation_class = RelationClassType.from_str(
                fields["relation_type"])
        except NotImplementedError as e:
            raise CommandExecutionError(str(e))

        if ".." in source_processors or ".." in target_processors:
            if ".." in source_processors:
                source_processor_names = obtain_matching_processors(
                    string_to_ast(processor_names,
                                  self._fields["source_processor"]),
                    self._all_processors)
            else:
                source_processor_names = [source_processors]
            if ".." in target_processors:
                target_processor_names = obtain_matching_processors(
                    string_to_ast(processor_names,
                                  self._fields["target_processor"]),
                    self._all_processors)
            else:
                target_processor_names = [target_processors]
            for s in source_processor_names:
                for t in target_processor_names:
                    self._fields["source_processor"] = s
                    self._fields["target_processor"] = t
                    process_relation(relation_class)
        else:
            process_relation(relation_class)