Expert Judgement Estimating Tool


    """
ExpertJudgementEstimationProgram

This program calculates estimated project effort using case points, actors, technical complexity
and environmental factors.

Dependencies:
    - None

Functions:
    - print_keys_of_dictionary(dictionary): Prints a given dictionary's keys.
    - key_exists(selection, dictionary): Checks whether inputted selection matches a dictionary's
      key
    - return_value_of_key(selection, dictionary): Returns the value of a dictionary's previosuly
      matched key.
    - choice_confirmed(choice): Determines if a user's choice confirms an action.

Usage:
    Run the file as a script and follow on-screen prompts.

"""

import sys

## Data ##

use_case_points = {
    "simple": 5,
    "average": 10,
    "complex": 15
}

actor_type = {
    "simple": 1,
    "average": 2,
    "complex": 3
}

technical_complexity_factors = {
    "Distributed system": 2,
    "Performance objectives": 2,
    "End-user efficiency": 1,
    "Complex processing": 1,
    "Reusable code": 1,
    "Easy to install": 0.5,
    "Easy to use": 0.5,
    "Portable": 2,
    "Easy to change": 1,
    "Concurrent use": 1,
    "Security": 1,
    "Access for third parties": 1,
    "Training needs": 1
}

enviornmental_factors = {
    "Familiar with the development process": 2,
    "Application experience": 2,
    "Object-oriented experience": 1,
    "Lead analyst capability": 1,
    "Motivation": 1,
    "Stable requirements": 0.5,
    "Part-time staff": 0.5,
    "Difficult programming language": 2
}


## Functions ##

def print_keys_of_dictionary(dictionary):
    """
    Prints a given dictionary's keys.

    Args:
        dictionay (dict): a dictionary with numerical values.

    Returns:
        None.

    """

    dictionary_str = ""

    for label in dictionary.keys():
        dictionary_str += "-" + str(label) + "\n"

    print(dictionary_str)


def key_exists(selection, dictionary):
    """
    Checks whether inputted selection matches a dictionary's key.

    Args:
        selection (str): a user-inputted string.
        dictionay (dict): a dictionary with numerical values.

    Returns:
        bool: True if match found, otherwise False.

    """

    for label in dictionary.keys():
        if selection.lower() == str(label).lower():
            return True

    return False


def return_value_of_key(selection, dictionary):
    """
    Returns the value of a dictionary's key.

    Args:
        selection (str): a user-inputted string.
        dictionay (dict): a dictionary with numerical values.

    Returns:
        int or None:
        - int of the value assigned to the matched key if matched.
        - None if key does not match input.

    """

    for key, value in dictionary.items():
        if selection.lower() == str(key).lower():
            return value

    return None


def choice_confirmed(user_choice):
    """
    Determines if a user's choice confirms an action.

    Args:
        choice (str): a user-inputted string.

    Returns:
        bool or None:
        - True if 'Y/y' inputted.
        - False if 'N/n' inputted.
        - None is anything else inputted.

    """

    if user_choice.lower() == "y":
        return True
    if user_choice.lower() == "n":
        return False

    return None


## CLI logic ##

if __name__ == "__main__":

    # Initialise variables
    T_FACT0R = 0
    E_FACTOR = 0

    print("*Use Case Points: \n")
    print_keys_of_dictionary(use_case_points)

    CHOOSING_USE_CASE = True
    CHOOSING_ACTOR_TYPE = True

    # Loop asking for use case point selection, capturing invalid input then re-prompting.
    while CHOOSING_USE_CASE:

        UUCW_str = input("Please select which 'use case point' best applies: ")

        if key_exists(UUCW_str, use_case_points):

            UUCW = return_value_of_key(UUCW_str, use_case_points)

            choice = input("\nWould you like to add another use case point? Type 'y' to add another or any other key to move on: ")
            
            if choice.lower() != "y":
                CHOOSING_USE_CASE = False
                
        else:
            print(f"\n!!'{UUCW_str}' is not a valid 'use case point'. Please re-enter!!\n")
        

    print("\n*Actor Type: \n")
    print_keys_of_dictionary(actor_type)

    # Loop asking for actor type selection, capturing invalid input then re-prompting.
    while CHOOSING_ACTOR_TYPE:

        INVALID_ENTRY = False

        UAW_str = input("Please select which 'actor type' best applies: ")
        
        if key_exists(UAW_str, actor_type):
            
            UAW = return_value_of_key(UAW_str, actor_type)
            
            choice = input("\nWould you like to add another use case point? Type 'y' to add another or any other key to move on: ")
            
            if choice.lower() != "y":
                CHOOSING_ACTOR_TYPE = False
        
        else:
            print(f"\n!!'{UAW_str}' is not a valid 'actor type'. Please re-enter!!\n")


    print("\n*Technical Complexity Factors: \n")
    print_keys_of_dictionary(technical_complexity_factors)

    # Calculate Unadjusted Use Case Points (UUCP) and store as UUCP variable
    UUCP = UUCW + UAW

    # Loop asking for technical complexity selection, capturing invalid input then re-prompting.
    while True:

        # Initialise or reset flags
        SELECTION_MADE = False
        INVALID_ENTRY = False
        WANTS_TO_QUIT = False

        t_factor_str = input("Please select which 'technical complexity factor' best applies: ")

        if key_exists(t_factor_str, technical_complexity_factors):

            T_FACT0R += return_value_of_key(t_factor_str, technical_complexity_factors)

            SELECTION_MADE = True

        else:
            print(f"\n!!'{t_factor_str}' is not a valid 'technical complexity factor'. Please "
                  "re-enter!!\n")

            INVALID_ENTRY = True

        if SELECTION_MADE:

            # Check if additional input is needed and re-prompt if invalid input.
            while True:

                choice = input("Would you like to add another factor? Type 'Y' or 'N': ")

                if choice_confirmed(choice):
                    SELECTION_MADE = False
                    break
                if choice_confirmed(choice) is False:
                    WANTS_TO_QUIT = True
                    break

                print("!!Choose a valid option!!")

        elif INVALID_ENTRY:
            continue

        else:
            break

        if WANTS_TO_QUIT:
            break

        continue

    TCF = 0.6 + (0.01*T_FACT0R)


    WANTS_TO_QUIT = False # Reset this flag as it is re-used below

    print("\n*Environmental Factors: \n")
    print_keys_of_dictionary(enviornmental_factors)


    # Loop asking for use environmantal complexity selection, capturing invalid input then
    # re-prompting.
    while True:
        # Initialise or reset flags
        SELECTION_MADE = False
        INVALID_ENTRY = False
        WANTS_TO_QUIT = False

        e_factor_str = input("Please select which 'environmental factor' best applies: ")

        if key_exists(e_factor_str, enviornmental_factors):

            score = input("What rating would you give this?")

            E_FACTOR += return_value_of_key(e_factor_str, enviornmental_factors)

            SELECTION_MADE = True

        else:
            print(f"\n!!'{e_factor_str}' is not a valid 'environmental factor'. Please "
                  "re-enter!!\n")

            INVALID_ENTRY = True

        if SELECTION_MADE:

            # Check if additional input is needed and re-prompt if invalid input.
            while True:

                choice = input("\nWould you like to add another factor? Type 'Y' or 'N': ")

                if choice_confirmed(choice):
                    SELECTION_MADE = False
                    break
                if choice_confirmed(choice) is False:
                    WANTS_TO_QUIT = True
                    break

                print("\n!!Choose a valid option!!\n")

        elif INVALID_ENTRY:
            continue

        else:
            break

        if WANTS_TO_QUIT:
            break

        continue

    EF = 1.4 + (-0.03*E_FACTOR)

    # Calculate the resultant use case points (UCP).
    UCP = UUCP*TCF*EF

    print(f"\n** THE ESTIMATED EFFORT FOR THIS PROJECT IS: {UCP} **")
    sys.exit()