NTT DATA's workflow orchestration services execute tasks subject to dependency constraints. Given a set of uniquely identified tasks in a dependency graph, implement the functional code required by the custom JUnit tests to calculate when every task can finish and identify the workflow's critical path.
The dependency graph is guaranteed to be a directed acyclic graph for normal inputs. A task can start immediately after all listed dependencies finish, and unlimited parallel execution is allowed.
Implement analyze_workflow(tasks). tasks is a list of objects containing:
id: a unique string task identifierduration: a non-negative integer execution timedepends_on: a list of task identifiersReturn an object containing completion_times, a mapping from task ID to earliest completion time, makespan, the maximum completion time, and critical_path, the task sequence producing the makespan. If a cycle exists, return empty completion times, a makespan of -1, and an empty critical path. For tied critical paths, return the lexicographically smallest task sequence.
def analyze_workflow(tasks):