Skip to content

Allocation Categories

AllocationCategories(baze)

Class used for handling allocation categories.

Source code in echo_baze/baze_root.py
def __init__(self, baze: e_bz.Baze) -> None:
    """Base class that all subclasses should inherit from.

    Parameters
    ----------
    baze : Baze
        Top level object carrying all functionality and the connection handler.

    """
    # check inputs
    if not isinstance(baze, e_bz.Baze):
        raise ValueError(f"baze must be of type Baze, not {type(baze)}")

    self.baze: e_bz.Baze = baze

get(allocation_types, output_type='dict')

Gets all allocation categories with detailed information.

The most useful keys/columns returned are:

  • id
  • available
  • enabled
  • excluded
  • performance
  • priority
  • sortIndex
  • uncategorized
  • color
  • parentCategoryId
  • parentCategoryName
  • parentAllocationTypeName
  • childrenCategoryIds: dict[str, list[int]] like {children_availability_type: [child_category_id, ...], ...}
  • childrenCategoryNames: dict[str, list[str]] like {children_availability_type: [child_category_name, ...], ...}

Parameters:

  • allocation_types

    (list[str]) –

    Desired allocation types to get the allocation categories from.

  • output_type

    (Literal['dict', 'DataFrame'], default: 'dict' ) –

    Output type of the data. Can be one of ["dict", "DataFrame"] By default "dict"

Returns:

  • dict[str, dict[str, dict[str, Any]]]

    In case output_type == "dict" it will return a dict with the following format: {allocation_type: {name: {attribute: value, ...}, ...}, ...}

  • DataFrame

    In case output_type == "DataFrame" it will return a DataFrame with the following format: index = MultiIndex with levels ["allocation_type", "category"], columns = [attribute, ...]

Source code in echo_baze/allocation_categories.py
@validate_call
def get(
    self,
    allocation_types: list[str],
    output_type: Literal["dict", "DataFrame"] = "dict",
) -> dict[str, dict[str, dict[str, Any]]] | DataFrame:
    """Gets all allocation categories with detailed information.

    The most useful keys/columns returned are:

    - id
    - available
    - enabled
    - excluded
    - performance
    - priority
    - sortIndex
    - uncategorized
    - color
    - parentCategoryId
    - parentCategoryName
    - parentAllocationTypeName
    - childrenCategoryIds: dict[str, list[int]] like {children_availability_type: [child_category_id, ...], ...}
    - childrenCategoryNames: dict[str, list[str]] like {children_availability_type: [child_category_name, ...], ...}

    Parameters
    ----------
    allocation_types: list[str]
        Desired allocation types to get the allocation categories from.
    output_type : Literal["dict", "DataFrame"], optional
        Output type of the data. Can be one of ["dict", "DataFrame"]
        By default "dict"

    Returns
    -------
    dict[str, dict[str, dict[str, Any]]]
        In case output_type == "dict" it will return a dict with the following format: {allocation_type: {name: {attribute: value, ...}, ...}, ...}
    DataFrame
        In case output_type == "DataFrame" it will return a DataFrame with the following format: index = MultiIndex with levels ["allocation_type", "category"], columns = [attribute, ...]

    """
    # getting the allocation types
    allocation_types_def = self.baze.allocations.types.get()
    allocation_type_ids = {k: v["id"] for k, v in allocation_types_def.items()}

    if wrong_types := set(allocation_types) - set(allocation_type_ids):
        raise ValueError(f"Invalid allocation types: {wrong_types}")

    # getting a list of allocation types including the parents of the wanted ones
    complete_allocation_types = []
    for allocation_type in list(allocation_types_def.keys()):
        # getting the parent type
        parent_allocation_type = allocation_types_def[allocation_type].get("linkedTypeName")
        if parent_allocation_type is not None:
            complete_allocation_types.append(parent_allocation_type)
        complete_allocation_types.append(allocation_type)
    complete_allocation_types = list(set(complete_allocation_types))

    # getting the allocation categories
    results = {}
    for allocation_type in complete_allocation_types:
        endpoint = f"allocationCategories?Id={allocation_type_ids[allocation_type]}"

        # getting the data
        request_result = self.baze.conn.get(endpoint)
        self._handle_http_errors(request_result)

        # converting to dict
        result: list[dict[str, Any]] = request_result.json()

        bool_keys = ["uncategorized", "available", "enabled", "performance", "excluded"]
        for entry, key in itertools.product(result, bool_keys):
            entry[key] = bool(entry[key])

        # converting to desired format
        result_dict = {entry["name"]: entry for entry in result}

        # adding to the results
        results[allocation_type] = result_dict

    # getting parentCategoryName
    for allocation_type, categories in results.items():
        # checking if the allocation type has a parent type
        parent_allocation_type = allocation_types_def[allocation_type].get("linkedTypeName")
        if parent_allocation_type is None:
            continue
        for attributes in categories.values():
            parent_category_id = attributes.get("parentCategoryId")
            if parent_category_id is None:
                continue
            for parent_category_name, parent_category_vals in results[parent_allocation_type].items():
                if parent_category_vals["id"] == parent_category_id:
                    attributes["parentCategoryName"] = parent_category_name
                    attributes["parentAllocationTypeName"] = parent_allocation_type
                    break

    # getting childrenCategoryIds and childrenCategoryNames
    for allocation_type, categories in results.items():
        # checking if the allocation type has a parent type
        parent_allocation_type = allocation_types_def[allocation_type].get("linkedTypeName")
        if parent_allocation_type is None:
            continue
        # iterating over the categories
        for category, attributes in categories.items():
            # adding this category to the children of the parent
            parent_category_name = attributes.get("parentCategoryName")
            if parent_category_name is None:
                continue
            if "childrenCategoryIds" not in results[parent_allocation_type][parent_category_name]:
                results[parent_allocation_type][parent_category_name]["childrenCategoryIds"] = {}
            if allocation_type not in results[parent_allocation_type][parent_category_name]["childrenCategoryIds"]:
                results[parent_allocation_type][parent_category_name]["childrenCategoryIds"][allocation_type] = []
            if "childrenCategoryNames" not in results[parent_allocation_type][parent_category_name]:
                results[parent_allocation_type][parent_category_name]["childrenCategoryNames"] = {}
            if allocation_type not in results[parent_allocation_type][parent_category_name]["childrenCategoryNames"]:
                results[parent_allocation_type][parent_category_name]["childrenCategoryNames"][allocation_type] = []

            # adding the current category to the children of the parent
            results[parent_allocation_type][parent_category_name]["childrenCategoryIds"][allocation_type].append(attributes["id"])
            results[parent_allocation_type][parent_category_name]["childrenCategoryNames"][allocation_type].append(category)

    # filtering only the wanted allocation types
    results = {allocation_type: categories for allocation_type, categories in results.items() if allocation_type in allocation_types}

    # returning on the desired format
    match output_type:
        case "dict":
            return results
        case "DataFrame":
            df = DataFrame.from_dict(
                {
                    (alloc_type, category): attributes
                    for alloc_type, categories in results.items()
                    for category, attributes in categories.items()
                },
                orient="index",
            )
            df.index.names = ["allocation_type", "category"]
            df = df.reset_index(drop=False).convert_dtypes(dtype_backend="pyarrow").set_index(["allocation_type", "category"])
            return df
        case _:
            raise ValueError(f"Invalid output_type: {output_type}")

get_default_children(allocation_types)

Gets the default child categories for the given allocation categories.

The most useful keys/columns returned are: - id - name

Parameters:

  • allocation_types

    (list[str]) –

    Desired allocation types to get the allocation categories from.

Returns:

  • dict[str, dict[str, dict[str, dict[str, str | int]]]]

    Dictionary with the default child categories in the format {allocation_type: {category_name: {children_allocation_type: {id: id, name: name}, ...}, ...}

Source code in echo_baze/allocation_categories.py
@validate_call
def get_default_children(
    self,
    allocation_types: list[str],
) -> dict[str, dict[str, dict[str, dict[str, str | int]]]]:
    """Gets the default child categories for the given allocation categories.

    The most useful keys/columns returned are:
    - id
    - name

    Parameters
    ----------
    allocation_types: list[str]
        Desired allocation types to get the allocation categories from.

    Returns
    -------
    dict[str, dict[str, dict[str, dict[str, str | int]]]]
        Dictionary with the default child categories in the format {allocation_type: {category_name: {children_allocation_type: {id: id, name: name}, ...}, ...}
    """
    # getting the allocation categories for the wanted types
    all_alloc_types = self.baze.allocations.types.get_ids()
    allocation_categories = self.get(allocation_types=list(all_alloc_types.keys()))

    missing_allocation_types = set(allocation_types) - set(allocation_categories)
    if missing_allocation_types:
        raise ValueError(f"Invalid allocation types: {missing_allocation_types}")

    # getting the default child categories
    default_child_categories = {}

    for allocation_type, categories in allocation_categories.items():
        if allocation_type not in allocation_types:
            continue
        # adding allocation_type to the default child categories
        default_child_categories[allocation_type] = {}
        for category, attributes in categories.items():
            # checking if the category has children
            if "childrenCategoryIds" not in attributes:
                continue

            if category not in default_child_categories[allocation_type]:
                default_child_categories[allocation_type][category] = {}

            for child_allocation_type in attributes["childrenCategoryIds"]:
                if child_allocation_type not in default_child_categories[allocation_type][category]:
                    default_child_categories[allocation_type][category][child_allocation_type] = {}

                # finding the default child categories
                if category in ALLOCATION_CHILD_DEFAULT_CATEGORIES:
                    # checking if the default child category exists in the children
                    if ALLOCATION_CHILD_DEFAULT_CATEGORIES[category] not in attributes["childrenCategoryNames"][child_allocation_type]:
                        raise ValueError(
                            f"Default child category {ALLOCATION_CHILD_DEFAULT_CATEGORIES[category]} not found in {category} children for {allocation_type}",
                        )
                    default_child = ALLOCATION_CHILD_DEFAULT_CATEGORIES[category]
                # trying to find a category that matches the parent .UND
                elif f"{category}.UND" in attributes["childrenCategoryNames"][child_allocation_type]:
                    default_child = f"{category}.UND"
                else:
                    default_child = attributes["childrenCategoryNames"][child_allocation_type][0]
                # getting the id of the default child category
                default_child_category_id = attributes["childrenCategoryIds"][child_allocation_type][
                    attributes["childrenCategoryNames"][child_allocation_type].index(default_child)
                ]
                # adding the default child category to the default child categories
                default_child_categories[allocation_type][category][child_allocation_type] = {
                    "id": default_child_category_id,
                    "name": default_child,
                }

    return default_child_categories

get_ids(allocation_types)

Gets a dictionary with all allocation category names and ids.

Returns:

  • dict[str, dict[str, int]]:

    Dictionary with all allocation categories in the format {allocation_type: {name: id, ...}, ...}

Source code in echo_baze/allocation_categories.py
@validate_call
def get_ids(self, allocation_types: list[str]) -> dict[str, dict[str, int]]:
    """Gets a dictionary with all allocation category names and ids.

    Returns
    -------
    dict[str, dict[str, int]]:
        Dictionary with all allocation categories in the format {allocation_type: {name: id, ...}, ...}

    """
    # getting the allocation types
    allocation_type_ids = self.baze.allocations.types.get_ids()

    if wrong_types := set(allocation_types) - set(allocation_type_ids):
        raise ValueError(f"Invalid allocation types: {wrong_types}")

    # getting the allocation categories
    results = {}
    for allocation_type in allocation_types:
        endpoint = f"allocationCategories?Id={allocation_type_ids[allocation_type]}"

        # getting the data
        request_result = self.baze.conn.get(endpoint)
        self._handle_http_errors(request_result)

        # converting to dict
        result: list[dict[str, Any]] = request_result.json()

        # converting to desired format
        result_dict = {entry["name"]: entry["id"] for entry in result}

        # adding to the results
        results[allocation_type] = result_dict

    return results