Graph analysis algorithms for Choreo.Planner projects.
Provides project-management-specific insights:
ready/1— tasks whose dependencies are all doneblocked/1— tasks with unresolved dependenciesorphans/1— tasks not in any milestonecritical_path/2— longest dependency chain (by estimate)bottlenecks/1— tasks ranked by transitive downstream impactvalidate/1— structural integrity checks
Summary
Functions
Returns tasks that have unresolved dependencies or blockers.
Returns tasks sorted by how many other tasks depend on them (directly or transitively).
Finds the critical path — the longest chain of dependent work by estimate.
Returns tasks with no :contains relationship (not in any milestone).
Returns tasks with status :backlog or :todo whose dependencies
are all :done.
Validates structural integrity.
Functions
@spec blocked(Choreo.Planner.t()) :: [{Choreo.Planner.t(), map()}]
Returns tasks that have unresolved dependencies or blockers.
Done and cancelled tasks are excluded.
Examples
iex> project = Choreo.Planner.new()
...> |> Choreo.Planner.add_task(:a, status: :backlog)
...> |> Choreo.Planner.add_task(:b, status: :backlog)
...> |> Choreo.Planner.depends_on(:a, :b)
iex> [{id, _}] = Choreo.Planner.Analysis.blocked(project)
iex> id
:a
@spec bottlenecks(Choreo.Planner.t()) :: [{Choreo.Planner.t(), non_neg_integer()}]
Returns tasks sorted by how many other tasks depend on them (directly or transitively).
High count = task blocks a lot of downstream work (bottleneck).
Examples
iex> project = Choreo.Planner.new()
...> |> Choreo.Planner.add_task(:core)
...> |> Choreo.Planner.add_task(:a)
...> |> Choreo.Planner.add_task(:b)
...> |> Choreo.Planner.depends_on(:a, :core)
...> |> Choreo.Planner.depends_on(:b, :core)
iex> [{top_id, top_count} | _rest] = Choreo.Planner.Analysis.bottlenecks(project)
iex> top_id
:core
iex> top_count
2
@spec critical_path( Choreo.Planner.t(), keyword() ) :: {:ok, [Choreo.Planner.t()], keyword()} | :error
Finds the critical path — the longest chain of dependent work by estimate.
If a milestone is given, only tasks under that milestone are considered.
Returns {:ok, path, total_estimate} or :error if the dependency graph
contains a cycle.
Examples
iex> project = Choreo.Planner.new()
...> |> Choreo.Planner.add_task(:a, estimate_hours: 2)
...> |> Choreo.Planner.add_task(:b, estimate_hours: 3)
...> |> Choreo.Planner.depends_on(:b, :a)
iex> {:ok, [:a, :b], total_estimate: 5} = Choreo.Planner.Analysis.critical_path(project)
iex> project = Choreo.Planner.new()
...> |> Choreo.Planner.add_task(:a)
...> |> Choreo.Planner.add_task(:b)
...> |> Choreo.Planner.add_task(:c)
...> |> Choreo.Planner.depends_on(:b, :a)
...> |> Choreo.Planner.depends_on(:a, :c)
...> |> Choreo.Planner.depends_on(:c, :b)
iex> Choreo.Planner.Analysis.critical_path(project)
:error
@spec orphans(Choreo.Planner.t()) :: [{Choreo.Planner.t(), map()}]
Returns tasks with no :contains relationship (not in any milestone).
Examples
iex> project = Choreo.Planner.new() |> Choreo.Planner.add_task(:t1)
iex> [{id, _}] = Choreo.Planner.Analysis.orphans(project)
iex> id
:t1
@spec ready(Choreo.Planner.t()) :: [{Choreo.Planner.t(), map()}]
Returns tasks with status :backlog or :todo whose dependencies
are all :done.
Examples
iex> project = Choreo.Planner.new()
...> |> Choreo.Planner.add_task(:t1, status: :todo)
iex> [{id, _}] = Choreo.Planner.Analysis.ready(project)
iex> id
:t1
@spec validate(Choreo.Planner.t()) :: list()
Validates structural integrity.
Returns a list of issues like {:error, :cycle_detected, nodes} or
{:warning, :unassigned_in_progress, task_id}.
Examples
iex> project = Choreo.Planner.new() |> Choreo.Planner.add_task(:a, status: :in_progress)
iex> Choreo.Planner.Analysis.validate(project)
[{:warning, :unassigned_in_progress, :a}]
iex> project = Choreo.Planner.new()
...> |> Choreo.Planner.add_task(:a)
...> |> Choreo.Planner.add_task(:b)
...> |> Choreo.Planner.add_task(:c)
...> |> Choreo.Planner.depends_on(:b, :a)
...> |> Choreo.Planner.depends_on(:a, :c)
...> |> Choreo.Planner.depends_on(:c, :b)
iex> [{:error, :cycle_detected, nodes}] = Choreo.Planner.Analysis.validate(project)
iex> is_list(nodes)
true
iex> :a in nodes
true