# `Choreo.UML.Analysis`
[🔗](https://github.com/code-shoily/choreo/blob/v0.14.1/lib/choreo/uml/analysis.ex#L1)

Architectural analysis suite for `Choreo.UML` class/struct diagrams.

Provides static analysis tools optimized for functional and OOP software design:
  * `cycles/1` — identifies circular dependency loops.
  * `broken_contracts/1` — flags incomplete behavior/protocol realizations.
  * `coupling_metrics/1` — computes Afferent/Efferent coupling and Instability.
  * `law_of_demeter_violations/1` — identifies structural Law of Demeter violations.
  * `validate/1` — returns structural and architectural issues as `{severity, message}` tuples.
  * `affected_by/2` — returns classes that transitively depend on a target class.
  * `depends_on/2` — returns classes that the target class transitively depends on.
  * `transitive_reduction/1` — finds redundant relationships implied by longer paths.

# `affected_by`

```elixir
@spec affected_by(Choreo.UML.t(), Choreo.UML.class_id()) :: [Choreo.UML.class_id()]
```

Returns all classes that transitively depend on the given class.

If you change `target`, these are the classes that could break.

## Examples

    iex> uml =
    ...>   Choreo.UML.new()
    ...>   |> Choreo.UML.add_class(:api)
    ...>   |> Choreo.UML.add_class(:service)
    ...>   |> Choreo.UML.add_class(:repo)
    ...>   |> Choreo.UML.add_relationship(:api, :service, type: :depends)
    ...>   |> Choreo.UML.add_relationship(:service, :repo, type: :depends)
    iex> Enum.sort(Choreo.UML.Analysis.affected_by(uml, :repo))
    [:api, :service]
    iex> Choreo.UML.Analysis.affected_by(uml, :api)
    []

This analysis answers the question: "What breaks if I change this class?"

# `broken_contracts`

```elixir
@spec broken_contracts(Choreo.UML.t()) :: [
  {Choreo.UML.class_id(), Choreo.UML.class_id(), [map()]}
]
```

Identifies components which have declared a `:realizes` or `:inherits` relationship
to a `:behavior`, `:protocol`, or `:interface` component but do not implement all
the functions specified by that contract.

Returns a list of tuples `[{component_id, contract_id, [missing_function_maps]}]`.

## Examples

    iex> contract = Choreo.UML.new()
    ...>   |> Choreo.UML.add_class(:auth, type: :behavior, functions: [%{name: "verify", arity: 1}])
    ...>   # Struct missing the arity 1 verify function:
    ...>   |> Choreo.UML.add_class(:provider, type: :struct, functions: [%{name: "verify", arity: 2}])
    ...>   |> Choreo.UML.add_relationship(:provider, :auth, type: :realizes)
    iex> [{:provider, :auth, [missing]}] = Choreo.UML.Analysis.broken_contracts(contract)
    iex> missing[:name]
    "verify"
    iex> missing[:arity]
    1

# `coupling_metrics`

```elixir
@spec coupling_metrics(Choreo.UML.t()) :: %{
  optional(Choreo.UML.class_id()) =&gt; %{
    afferent: non_neg_integer(),
    efferent: non_neg_integer(),
    instability: float()
  }
}
```

Computes coupling and stability metrics for all components in the UML diagram.

Returns a map of component IDs to:
  * `:afferent` — number of incoming dependencies (who depends on me)
  * `:efferent` — number of outgoing dependencies (who do I depend on)
  * `:instability` — Ce / (Ca + Ce). Stable core modules approach 0.0, unstable leaf modules approach 1.0.

## Examples

    iex> uml =
    ...>   Choreo.UML.new()
    ...>   |> Choreo.UML.add_class(:a)
    ...>   |> Choreo.UML.add_class(:b)
    ...>   |> Choreo.UML.add_relationship(:a, :b, type: :associates)
    iex> metrics = Choreo.UML.Analysis.coupling_metrics(uml)
    iex> metrics[:a]
    %{afferent: 0, efferent: 1, instability: 1.0}
    iex> metrics[:b]
    %{afferent: 1, efferent: 0, instability: 0.0}

# `cycles`

```elixir
@spec cycles(Choreo.UML.t()) :: [[Choreo.UML.class_id()]]
```

Identifies all circular dependency paths in the UML diagram.

Cycles in structural diagrams indicate high coupling and are a major source
of compilation cascades in Elixir applications.

Returns a list of cycles, where each cycle is a list of node IDs
starting at the canonical smallest node and listing each member once.

## Examples

    iex> uml =
    ...>   Choreo.UML.new()
    ...>   |> Choreo.UML.add_class(:a)
    ...>   |> Choreo.UML.add_class(:b)
    ...>   |> Choreo.UML.add_relationship(:a, :b, type: :associates)
    ...>   |> Choreo.UML.add_relationship(:b, :a, type: :depends)
    iex> Choreo.UML.Analysis.cycles(uml)
    [[:a, :b]]

# `depends_on`

```elixir
@spec depends_on(Choreo.UML.t(), Choreo.UML.class_id()) :: [Choreo.UML.class_id()]
```

Returns all classes that the given class transitively depends on.

These are the classes `target` cannot function without, following the
directed relationships in the diagram.

## Examples

    iex> uml =
    ...>   Choreo.UML.new()
    ...>   |> Choreo.UML.add_class(:api)
    ...>   |> Choreo.UML.add_class(:service)
    ...>   |> Choreo.UML.add_class(:repo)
    ...>   |> Choreo.UML.add_relationship(:api, :service, type: :depends)
    ...>   |> Choreo.UML.add_relationship(:service, :repo, type: :depends)
    iex> Enum.sort(Choreo.UML.Analysis.depends_on(uml, :api))
    [:repo, :service]
    iex> Choreo.UML.Analysis.depends_on(uml, :repo)
    []

This analysis answers the question: "What does this class depend on?"

# `law_of_demeter_violations`

```elixir
@spec law_of_demeter_violations(Choreo.UML.t()) :: [
  {Choreo.UML.class_id(), Choreo.UML.class_id(), Choreo.UML.class_id()}
]
```

Identifies all Law of Demeter violations in the UML class diagram.

A violation occurs when a class `A` has a direct relationship to a class `C`,
but also has a path through an intermediate class `B` (i.e., `A -> B`, `B -> C`,
and `A -> C`).

Returns a list of triplets `[{class_a, class_b, class_c}]` representing the violations.

## Examples

    iex> uml =
    ...>   Choreo.UML.new()
    ...>   |> Choreo.UML.add_class(:a)
    ...>   |> Choreo.UML.add_class(:b)
    ...>   |> Choreo.UML.add_class(:c)
    ...>   |> Choreo.UML.add_relationship(:a, :b, type: :associates)
    ...>   |> Choreo.UML.add_relationship(:b, :c, type: :associates)
    ...>   |> Choreo.UML.add_relationship(:a, :c, type: :associates)
    iex> Choreo.UML.Analysis.law_of_demeter_violations(uml)
    [{:a, :b, :c}]

# `transitive_reduction`

```elixir
@spec transitive_reduction(Choreo.UML.t()) :: [
  {Choreo.UML.class_id(), Choreo.UML.class_id()}
]
```

Identifies redundant relationships that are implied by a longer path.

If `api -> service`, `service -> repo`, and `api -> repo` all exist,
the direct `api -> repo` edge is redundant because it is implied by the
transitive path.

Returns a list of `{from, to}` tuples. On cyclic diagrams returns an
empty list because every edge in a cycle is structurally required.

## Examples

    iex> uml =
    ...>   Choreo.UML.new()
    ...>   |> Choreo.UML.add_class(:api)
    ...>   |> Choreo.UML.add_class(:service)
    ...>   |> Choreo.UML.add_class(:repo)
    ...>   |> Choreo.UML.add_relationship(:api, :service, type: :depends)
    ...>   |> Choreo.UML.add_relationship(:service, :repo, type: :depends)
    ...>   |> Choreo.UML.add_relationship(:api, :repo, type: :depends)
    iex> Choreo.UML.Analysis.transitive_reduction(uml)
    [{:api, :repo}]

This analysis answers the question: "Which explicit relationships are redundant?"

# `validate`

```elixir
@spec validate(Choreo.UML.t()) :: [{:error | :warning, String.t()}]
```

Validates a UML diagram and returns a list of issues.

Checks for:
  * Circular dependencies (`:error`)
  * Broken contracts (`:error`)
  * Isolated classes (`:warning`)
  * Law of Demeter violations (`:warning`)

## Examples

    iex> uml =
    ...>   Choreo.UML.new()
    ...>   |> Choreo.UML.add_class(:a)
    ...>   |> Choreo.UML.add_class(:b)
    ...>   |> Choreo.UML.add_relationship(:a, :b, type: :associates)
    ...>   |> Choreo.UML.add_relationship(:b, :a, type: :depends)
    iex> Enum.any?(Choreo.UML.Analysis.validate(uml), fn {sev, _} -> sev == :error end)
    true

---

*Consult [api-reference.md](api-reference.md) for complete listing*
