Beispiel #1
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))
Beispiel #2
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
    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}'"
            )
Beispiel #4
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]
    def _process_row(self, fields: Dict[str, Any], subrow=None) -> None:
        """
        :param fields:
        :param subrow:
        :return:
        """
        # InterfaceType must exist
        try:
            self._get_factor_type_from_field(None, "interface")
        except CommandExecutionError as e:
            self._add_issue(IType.ERROR, str(e))

        # (LCIA) Indicator must exist
        indicator = self._glb_idx.get(
            Indicator.partial_key(fields["lcia_indicator"]))
        if len(indicator) == 1:
            pass
        elif len(indicator) == 0:
            self._add_issue(
                IType.ERROR,
                f"Indicator with name '{fields['lcia_indicator']}' not found" +
                subrow_issue_message(subrow))
            return
        else:
            self._add_issue(
                IType.WARNING,
                f"Indicator with name '{fields['lcia_indicator']}' found {len(indicator)} times"
                + subrow_issue_message(subrow))
            return

        # Store LCIA Methods as a new variable.
        # TODO Use it to prepare a pd.DataFrame previous to calculating Indicators (after solving). Use "to_pickable"
        lcia_methods = self._state.get("_lcia_methods")
        if not lcia_methods:
            lcia_methods = PartialRetrievalDictionary()
            self._state.set("_lcia_methods", lcia_methods)
        _ = dict(m=fields["lcia_method"],
                 d=fields["lcia_indicator"],
                 h=fields["lcia_horizon"],
                 i=fields["interface"])
        lcia_methods.put(
            _, (fields["interface_unit"], fields["lcia_coefficient"]))
Beispiel #6
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 _process_row(self, fields: Dict[str, Any], subrow=None) -> None:
        """
        Create and register Benchmark object

        :param fields:
        """
        name = fields["benchmark"]
        benchmark_group = fields["benchmark_group"]
        stakeholders = fields["stakeholders"]
        b = self._glb_idx.get(Benchmark.partial_key(name=name))
        if len(b) == 1:
            b = b[0]
        elif len(b) == 0:
            b = Benchmark(name, benchmark_group,
                          stakeholders.split(",") if stakeholders else [])
            self._glb_idx.put(b.key(), b)
        else:
            self._add_issue(
                IType.ERROR,
                f"There are {len(b)} instances of the Benchmark '{name}'" +
                subrow_issue_message(subrow))
            return

        # Add range, if not repeated
        category = fields["category"]
        if category not in b.ranges:
            b.ranges[category] = create_dictionary(
                data=dict(range=fields["range"],
                          unit=fields["unit"],
                          category=category,
                          label=fields["label"],
                          description=fields["description"]))
        else:
            self._add_issue(
                IType.WARNING, f"Range with category '{category}' repeated" +
                subrow_issue_message(subrow))
Beispiel #8
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
Beispiel #9
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"])
Beispiel #10
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)
Beispiel #11
0
    def _process_row(self, field_values: Dict[str, Any], subrow=None) -> None:
        """
        Process a dictionary representing a row of the InterfaceTypes command. The dictionary can come directly from
        the worksheet or from a dataset.

        :param field_values: dictionary
        """

        # Read variables
        ft_h_name = field_values.get(
            "interface_type_hierarchy",
            "_default")  # "_default" InterfaceType Hierarchy NAME <<<<<<
        ft_name = field_values.get("interface_type")
        ft_sphere = field_values.get("sphere")
        ft_roegen_type = field_values.get("roegen_type")
        ft_parent = field_values.get("parent_interface_type")
        ft_formula = field_values.get("formula")
        ft_description = field_values.get("description")
        ft_unit = field_values.get("unit")
        ft_opposite_processor_type = field_values.get(
            "opposite_processor_type")
        ft_level = field_values.get("level")
        ft_attributes = field_values.get("attributes", {})
        print(str(type(ft_attributes)))
        if ft_attributes:
            try:
                ft_attributes = dictionary_from_key_value_list(
                    ft_attributes, self._glb_idx)
            except Exception as e:
                self._add_issue(IType.ERROR,
                                str(e) + subrow_issue_message(subrow))
                return
        else:
            ft_attributes = {}

        # Process
        # Mandatory fields
        if not ft_h_name:
            self._add_issue(
                IType.WARNING,
                "Empty interface type hierarchy name. It is recommended to specify one, assuming '_default'."
                + subrow_issue_message(subrow))
            ft_h_name = "_default"

        if not ft_name:
            self._add_issue(
                IType.ERROR, "Empty interface type name. Skipped." +
                subrow_issue_message(subrow))
            return

        # Check if a hierarchy of interface types by the name <ft_h_name> exists, if not, create it and register it
        hie = self._glb_idx.get(Hierarchy.partial_key(name=ft_h_name))
        if not hie:
            hie = Hierarchy(name=ft_h_name, type_name="interfacetype")
            self._glb_idx.put(hie.key(), hie)
        else:
            hie = hie[0]

        # If parent defined, check if it exists
        # (it must be registered both in the global registry AND in the hierarchy)
        if ft_parent:
            parent = self._glb_idx.get(FactorType.partial_key(ft_parent))
            if len(parent) > 0:
                for p in parent:
                    if p.hierarchy == hie:
                        parent = p
                        break
                if not isinstance(parent, FactorType):
                    self._add_issue(
                        IType.ERROR,
                        f"Parent interface type name '{ft_parent}' not found in hierarchy '{ft_h_name}"
                        + subrow_issue_message(subrow))
                    return
            else:
                self._add_issue(
                    IType.ERROR,
                    f"Parent interface type name '{ft_parent}' not found" +
                    subrow_issue_message(subrow))
                return
            # Double check, it must be defined in "hie"
            if ft_parent not in hie.codes:
                self._add_issue(
                    IType.ERROR,
                    f"Parent interface type name '{ft_parent}' not registered in the hierarchy '{ft_h_name}'"
                    + subrow_issue_message(subrow))
                return
        else:
            parent = None

        # Check if FactorType exists
        ft = self._glb_idx.get(FactorType.partial_key(ft_name))
        if len(ft) == 0:
            # TODO Compile and CONSIDER attributes (on the FactorType side)
            roegen_type = None
            if ft_roegen_type:
                roegen_type = FlowFundRoegenType.flow if strcmp(
                    ft_roegen_type, "flow") else FlowFundRoegenType.fund

            ft = FactorType(
                ft_name,
                parent=parent,
                hierarchy=hie,
                roegen_type=roegen_type,
                tags=None,  # No tags
                attributes=dict(unit=ft_unit,
                                description=ft_description,
                                level=ft_level,
                                **ft_attributes),
                expression=ft_formula,
                sphere=ft_sphere,
                opposite_processor_type=ft_opposite_processor_type)
            # Simple name
            self._glb_idx.put(FactorType.partial_key(ft_name, ft.ident), ft)
            if not strcmp(ft_name, ft.full_hierarchy_name()):
                self._glb_idx.put(
                    FactorType.partial_key(ft.full_hierarchy_name(), ft.ident),
                    ft)
        else:
            self._add_issue(
                IType.WARNING,
                f"Interface type name '{ft_name}' already registered" +
                subrow_issue_message(subrow))
            return
    def _process_row(self, field_values: Dict[str, Any], subrow=None) -> None:

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

        # Process specific fields

        # Obtain the parent: it must exist. It could be created dynamically but it's important to specify attributes
        if field_values.get("parent_processor"):
            try:
                parent_processor = self._get_processor_from_field(
                    "parent_processor")
                # parents = find_processors_matching_name(parent_processor)
                # if len(parents) > 1:
                #     self._add_issue(IType.WARNING,
                #                     f"Parent processor '{parent_processor}' not unique. Matches: {', '.join(p.hierarchical_names[0] for p in parents)}. Skipped." + subrow_issue_message(subrow))
                #     return
            except CommandExecutionError:
                self._add_issue(
                    IType.ERROR,
                    f"Specified parent processor, '{field_values.get('parent_processor')}', does not exist"
                    + subrow_issue_message(subrow))
                return
        else:
            parent_processor = None

        behave_as_processor: Optional[Processor] = None
        if field_values.get("behave_as_processor"):
            try:
                behave_as_processor = self._get_processor_from_field(
                    "behave_as_processor")
            except CommandExecutionError:
                self._add_issue(
                    IType.WARNING,
                    f"Specified 'behave as' processor, '{field_values.get('behave_as_processor')}', does not exist, value ignored"
                    + subrow_issue_message(subrow))

        # Find or create processor and REGISTER it in "glb_idx"
        # TODO Now, only Simple name allowed
        # TODO Improve allowing hierarchical names, and hierarchical names with wildcards
        pgroup = field_values.get("processor_group")

        # Get internal and user-defined attributes in one dictionary
        attributes = {
            c.name: field_values[c.name]
            for c in self._command_fields if c.attribute_of == Processor
        }
        attributes.update(field_values["attributes"])
        attributes["processor_group"] = pgroup

        # Needed to support the new name of the field, "Accounted" (previously it was "InstanceOrArchetype")
        # (internally the values have the same meaning, "Instance" for a processor which has to be accounted,
        # "Archetype" for a processor which hasn't)
        v = attributes.get("instance_or_archetype", None)
        if strcmp(v, "Yes"):
            v = "Instance"
        elif strcmp(v, "No"):
            v = "Archetype"
        if v:
            attributes["instance_or_archetype"] = v

        name = field_values["processor"]
        p_names, _ = obtain_name_parts(name)

        geolocation = Geolocation.create(field_values["geolocation_ref"],
                                         field_values["geolocation_code"])

        ps = find_processors_matching_name(name, self._glb_idx)
        more_than_one = len(ps) > 1
        simple = len(p_names) == 1
        exists = True if len(ps) == 1 else False
        # SIMPLE? EXISTS? PARENT? ACTION:
        # Yes     Yes     Yes     NEW; HANG FROM PARENT
        # Yes     Yes     No      Warning: repeated
        # Yes     No      Yes     NEW; HANG FROM PARENT
        # Yes     No      No      NEW
        # No      Yes     Yes     Warning: cannot hang from parent
        # No      Yes     No      Warning: repeated AND not simple not allowed
        # No      No      Yes     Warning: cannot create more than one processor AND not simple not allowed
        # No      No      No      Warning: cannot create more than one processor AND not simple not allowed

        create_new = False
        if not simple:
            if not parent_processor:
                self._add_issue(
                    IType.WARNING,
                    f"When a processor does not have parent, the name must be simple. Skipped."
                    + subrow_issue_message(subrow))
                return
        else:
            if exists and not parent_processor:
                self._add_issue(
                    IType.WARNING,
                    f"Repeated declaration of {name}. Skipped." +
                    subrow_issue_message(subrow))
                return
            create_new = True

        if create_new:
            p = find_or_create_processor(state=self._glb_idx,
                                         name=name,
                                         proc_attributes=attributes,
                                         proc_location=geolocation)
        else:
            if exists:
                p = ps[0]

        # Add to ProcessorsGroup, if specified
        if pgroup:
            p_set = self._p_sets.get(pgroup, ProcessorsSet(pgroup))
            self._p_sets[pgroup] = p_set
            if p_set.append(
                    p, self._glb_idx
            ):  # Appends codes to the pset if the processor was not member of the pset
                p_set.append_attributes_codes(field_values["attributes"])

        # If geolocation specified, check if it exists
        # Inside it, check it the code exists
        if p.geolocation and p.geolocation.reference:
            # Geographical reference
            gr = self._glb_idx.get(
                GeographicReference.partial_key(name=p.geolocation.reference))
            if len(gr) == 0:
                self._add_issue(
                    IType.ERROR,
                    f"Geographical reference {p.geolocation.reference} not found "
                    + subrow_issue_message(subrow))
                return
            if p.geolocation.reference and not p.geolocation.code:
                self._add_issue(
                    IType.ERROR,
                    f"Geographical reference was specified but not the code in it "
                    + subrow_issue_message(subrow))
                return
            geo_id = p.geolocation.code
            try:
                url = gr[0].attributes["data_location"]
            except:
                self._add_issue(
                    IType.ERROR,
                    f"URL not found in geographical reference {p.geolocation.reference} "
                    + subrow_issue_message(subrow))
                return
            try:
                j, ids = read_geojson(
                    url
                )  # READ the file!! (or get it from cache). Could take some time...
            except:
                self._add_issue(
                    IType.ERROR,
                    f"URL {url} in reference {p.geolocation.reference} could not be read "
                    + subrow_issue_message(subrow))
                return
            if geo_id not in ids:
                self._add_issue(
                    IType.WARNING,
                    f"Could not find code {geo_id} in file {url}, geographical reference {p.geolocation.reference} "
                    + subrow_issue_message(subrow))

        # Add Relationship "part-of" if parent was specified
        # The processor may have previously other parent processors that will keep its parentship
        if parent_processor:
            # Create "part-of" relationship
            if len(
                    self._glb_idx.get(
                        ProcessorsRelationPartOfObservation.partial_key(
                            parent_processor, p))) > 0:
                self._add_issue(
                    IType.WARNING,
                    f"{p.name} is already part-of {parent_processor.name}. Skipped."
                    + subrow_issue_message(subrow))
                return

            o1 = ProcessorsRelationPartOfObservation.create_and_append(
                parent_processor,
                p,
                None,
                behave_as=behave_as_processor,
                weight=field_values.get("parent_processor_weight"))  # Part-of
            self._glb_idx.put(o1.key(), o1)
            for hname in parent_processor.full_hierarchy_names(self._glb_idx):
                p_key = Processor.partial_key(f"{hname}.{p.name}", p.ident)
                if attributes:
                    p_key.update({
                        k: ("" if v is None else v)
                        for k, v in attributes.items()
                    })
                self._glb_idx.put(p_key, p)
    def _process_row(self, fields: Dict[str, Any], subrow=None) -> None:
        origin_interface_types = self._get_factor_types_from_field("source_hierarchy", "source_interface_type")
        destination_interface_types = self._get_factor_types_from_field("target_hierarchy", "target_interface_type")

        origin_processor: Optional[Processor] = None
        if fields["source_context"]:
            origin_processor = self._get_processor_from_field("source_context")

        destination_processor: Optional[Processor] = None
        if fields["target_context"]:
            destination_processor = self._get_processor_from_field("target_context")

        # Check that the interface types are from different hierarchies (warn if not; not error)
        for origin_interface_type in origin_interface_types:
            for destination_interface_type in destination_interface_types:
                if origin_interface_type.hierarchy == destination_interface_type.hierarchy:
                    self._add_issue(IType.WARNING, f"The interface types '{origin_interface_type.name}' and "
                                                   f"'{destination_interface_type.name}' are in the same hierarchy"+subrow_issue_message(subrow))

                observer = find_or_create_observer(Observer.no_observer_specified, self._glb_idx)

                # Check existence of ScaleChange
                key = FactorTypesRelationUnidirectionalLinearTransformObservation.partial_key(origin_interface_type, destination_interface_type, origin_processor, destination_processor, observer)
                tmp = self._glb_idx.get(key)
                if len(tmp) == 0:
                    # Create the directed Scale (Linear "Transformation") Relationship
                    o = FactorTypesRelationUnidirectionalLinearTransformObservation.create_and_append(
                        origin_interface_type, destination_interface_type, fields["scale"],
                        origin_processor, destination_processor,
                        fields["source_unit"], fields["target_unit"],
                        observer)
                    self._glb_idx.put(o.key(), o)
                elif len(tmp) > 0:
                    self._add_issue(IType.WARNING, f"The ScaleChange {tmp[0]} has already been defined with weight {tmp[0]._weight}")