|
import os |
|
import json |
|
import logging |
|
from src.wbs.wbs_task import WBSTask, WBSProject |
|
from src.uuid_util.is_valid_uuid import is_valid_uuid |
|
|
|
logger = logging.getLogger(__name__) |
|
|
|
class WBSPopulate: |
|
@staticmethod |
|
def project_from_level1_json(path_level1_json: str) -> WBSProject: |
|
""" |
|
Create a WBSProject from a level 1 JSON file. |
|
""" |
|
if not isinstance(path_level1_json, str): |
|
raise ValueError("Invalid path_level1_json.") |
|
|
|
with open(path_level1_json) as f: |
|
d = json.load(f) |
|
if not isinstance(d, dict): |
|
raise ValueError("Expected a dictionary in the JSON file.") |
|
|
|
root_task_id = d.get('id', None) |
|
project_title = d.get('project_title', None) |
|
final_deliverable = d.get('final_deliverable', None) |
|
|
|
if not is_valid_uuid(root_task_id): |
|
logger.error(f"Expected valid UUID, but got root_task_id: {root_task_id}") |
|
|
|
root_task = WBSTask( |
|
id = root_task_id, |
|
description=project_title, |
|
) |
|
root_task.set_field('final_deliverable', final_deliverable) |
|
return WBSProject(root_task) |
|
|
|
@staticmethod |
|
def extend_project_with_level2_json(wbs_project: WBSProject, path_level2_json: str): |
|
""" |
|
Grow the tree hierarchy with tasks from a WBS Level 2 JSON file. |
|
""" |
|
if not isinstance(wbs_project, WBSProject): |
|
raise ValueError("Expected a WBSProject object.") |
|
if not isinstance(path_level2_json, str): |
|
raise ValueError("Invalid path_level2_json.") |
|
|
|
with open(path_level2_json) as f: |
|
task_item_list = json.load(f) |
|
if not isinstance(task_item_list, list): |
|
raise ValueError("Expected a list of tasks in the JSON file.") |
|
|
|
for task_index, task_json in enumerate(task_item_list): |
|
task_id = task_json.get('id', None) |
|
if not is_valid_uuid(task_id): |
|
logger.error(f"Expected valid UUID, but got task_id: {task_id} for task_index: {task_index}") |
|
task_id = "MISSING_TASK_ID" |
|
|
|
major_phase_title = task_json.get('major_phase_title', None) |
|
if not major_phase_title: |
|
logger.error(f"Missing 'major_phase_title' for task with id: {task_id} for task_index: {task_index}") |
|
major_phase_title = "MISSING_MAJOR_PHASE_TITLE" |
|
|
|
task = WBSTask( |
|
id=task_id, |
|
description=major_phase_title |
|
) |
|
wbs_project.root_task.task_children.append(task) |
|
|
|
subtask_list = task_json.get('subtasks', None) |
|
if not subtask_list: |
|
logger.error(f"Missing 'subtasks' for task with id: {task_id}") |
|
subtask_list = [] |
|
|
|
for subtask_json in subtask_list: |
|
subtask_id = subtask_json.get('id', None) |
|
if not is_valid_uuid(subtask_id): |
|
logger.error(f"Expected valid UUID, but got subtask_id: {subtask_id} for task with id: {task_id}") |
|
|
|
subtask_description = subtask_json.get('description', None) |
|
if not subtask_description: |
|
logger.error(f"Missing 'description' for subtask with id: {subtask_id}") |
|
subtask_description = "MISSING_SUBTASK_DESCRIPTION" |
|
|
|
subtask = WBSTask( |
|
id=subtask_id, |
|
description=subtask_description |
|
) |
|
task.task_children.append(subtask) |
|
|
|
@staticmethod |
|
def extend_project_with_dependencies_json(wbs_project: WBSProject, path_dependencies_json: str): |
|
""" |
|
Enrich the tree hierarchy with tasks from a dependencies JSON file. |
|
|
|
Establish dependencies between tasks, with explanations why these dependencies exists. |
|
""" |
|
if not isinstance(wbs_project, WBSProject): |
|
raise ValueError("Expected a WBSProject object.") |
|
if not isinstance(path_dependencies_json, str): |
|
raise ValueError("Invalid path_dependencies_json.") |
|
|
|
with open(path_dependencies_json) as f: |
|
d = json.load(f) |
|
if not isinstance(d, dict): |
|
raise ValueError("Expected a dictionary in the JSON file.") |
|
|
|
task_item_list = d.get('task_dependency_details', []) |
|
|
|
for task_json in task_item_list: |
|
dependent_task_id = task_json['dependent_task_id'] |
|
dependent_task = wbs_project.root_task.find_task_by_id(dependent_task_id) |
|
if not dependent_task: |
|
logger.debug(f"ERROR: Task with id {dependent_task_id} not found. Cannot set dependencies.") |
|
continue |
|
depends_on_task_id_list = task_json['depends_on_task_id_list'] |
|
|
|
|
|
for depends_on_task_id in depends_on_task_id_list: |
|
depends_on_task = wbs_project.root_task.find_task_by_id(depends_on_task_id) |
|
if not depends_on_task: |
|
logger.debug(f"ERROR: Task with id {depends_on_task_id} not found. Cannot set dependency for task {dependent_task_id}.") |
|
continue |
|
|
|
dependent_task.set_field('depends_on_task_ids', depends_on_task_id_list) |
|
|
|
depends_on_task_explanation_list = task_json['depends_on_task_explanation_list'] |
|
dependent_task.set_field('depends_on_task_explanations', depends_on_task_explanation_list) |
|
|
|
@staticmethod |
|
def extend_project_with_durations_json(wbs_project: WBSProject, path_durations_json: str): |
|
""" |
|
Enrich the task hierarchy with time estimates from a task_durations JSON file. |
|
""" |
|
if not isinstance(wbs_project, WBSProject): |
|
raise ValueError("Expected a WBSProject object.") |
|
if not isinstance(path_durations_json, str): |
|
raise ValueError("Invalid path_dependencies_json.") |
|
|
|
with open(path_durations_json) as f: |
|
task_duration_list = json.load(f) |
|
if not isinstance(task_duration_list, list): |
|
raise ValueError("Expected a list in the JSON file.") |
|
|
|
|
|
for task_duration_index, task_duration_json in enumerate(task_duration_list): |
|
task_id = task_duration_json.get('task_id', None) |
|
if not is_valid_uuid(task_id): |
|
logger.error(f"Expected valid UUID, but got task_id: {task_id} for task_duration_index: {task_duration_index}") |
|
continue |
|
task = wbs_project.root_task.find_task_by_id(task_id) |
|
if not task: |
|
logger.error(f"Task with id {task_id} not found in WBSProject. Cannot set duration fields.") |
|
continue |
|
delay_risks = task_duration_json.get('delay_risks', None) |
|
if delay_risks: |
|
task.set_field('delay_risks', delay_risks) |
|
mitigation_strategy = task_duration_json.get('mitigation_strategy', None) |
|
if mitigation_strategy: |
|
task.set_field('mitigation_strategy', mitigation_strategy) |
|
days_min = task_duration_json.get('days_min', None) |
|
if days_min: |
|
task.set_field('days_min', days_min) |
|
days_max = task_duration_json.get('days_max', None) |
|
if days_max: |
|
task.set_field('days_max', days_max) |
|
days_realistic = task_duration_json.get('days_realistic', None) |
|
if days_realistic: |
|
task.set_field('days_realistic', days_realistic) |
|
|
|
@staticmethod |
|
def extend_project_with_decomposed_tasks_json(wbs_project: WBSProject, path_decomposed_tasks_json: str): |
|
""" |
|
Enrich the task hierarchy with more subtasks from a decomposed_tasks JSON file. |
|
""" |
|
if not isinstance(wbs_project, WBSProject): |
|
raise ValueError("Expected a WBSProject object.") |
|
if not isinstance(path_decomposed_tasks_json, str): |
|
raise ValueError("Invalid path_decomposed_tasks_json.") |
|
|
|
with open(path_decomposed_tasks_json) as f: |
|
task_list = json.load(f) |
|
if not isinstance(task_list, list): |
|
raise ValueError("Expected a list in the JSON file.") |
|
|
|
|
|
for task_index, task_json in enumerate(task_list): |
|
task_id = task_json.get('id', None) |
|
if not is_valid_uuid(task_id): |
|
logger.error(f"Expected valid UUID, but got task_id: {task_id} for task_index: {task_index}. Cannot create subtask.") |
|
continue |
|
task_parent_id = task_json.get('parent_id', None) |
|
if not is_valid_uuid(task_parent_id): |
|
logger.error(f"Expected valid UUID, but got task_parent_id: {task_parent_id} for task_index: {task_index}. Cannot create subtask.") |
|
continue |
|
subtask_name = task_json.get('name', None) |
|
if not subtask_name: |
|
logger.error(f"Missing 'name' for task with id: {task_id}.") |
|
subtask_name = "MISSING_SUBTASK_NAME" |
|
|
|
parent_task = wbs_project.root_task.find_task_by_id(task_parent_id) |
|
if not parent_task: |
|
logger.error(f"Task with id {task_parent_id} not found. Cannot create subtask for child task {task_id}.") |
|
continue |
|
task = WBSTask( |
|
id=task_id, |
|
description=subtask_name |
|
) |
|
subtask_detailed_description = task_json.get('description', None) |
|
if subtask_detailed_description: |
|
task.set_field('detailed_description', subtask_detailed_description) |
|
subtask_resources_needed = task_json.get('resources_needed', None) |
|
if subtask_resources_needed: |
|
task.set_field('resources_needed', subtask_resources_needed) |
|
parent_task.task_children.append(task) |
|
|
|
|
|
|