Skip to content

Problem Tree

A problem tree puts the problem a program exists to solve in the middle, its causes below as roots, and its effects above as branches. It comes before the theory of change: the roots say what the program could work on, and the branches say what harm it should reduce. Drawn from an impact model, it is one more projection of the same model, and it checks the solution against the problem: every cause and every effect should be addressed by an outcome or set aside with a reason.

impact-model report problem-tree impact-model.yaml        # Markdown, with a Mermaid diagram
impact-model report problem-tree-d2 impact-model.yaml     # the same, with a D2 diagram
impact-model report problem-tree-json impact-model.yaml   # the diagram document itself, as JSON

The parts and where they live

Diagram In the model Notes
Core problem, in the middle context.problemStatement The problem the program responds to
Causes (roots), below causes Each with a level: immediate, underlying, or structural
A cause leading to another a cause's parents The causes it contributes to; none means it leads straight to the core problem
Effects (branches), above effects What the core problem leads to
An effect leading to another an effect's parents The effects it follows from; none means it follows straight from the core problem
What addresses a cause an outcome's addressesCauses Usually earlier outcomes: what the program works on
What relieves an effect an outcome's addressesEffects Usually long-term and impact outcomes: the harm the program reduces
Set aside outOfScope.reason on a cause or effect Real, but not something this program takes on

The impact model is JSON first: a model file is JSON or YAML, read against one JSON Schema generated from the Go types.

context:
  problemStatement: Poor eating health
causes:
  - id: cause-busy
    statement: Too busy to cook
    level: immediate
  - id: cause-fast-food
    statement: Fast food is everywhere
    level: underlying
    parents: [cause-busy]
  - id: cause-poverty
    statement: Poverty
    level: structural
    outOfScope:
      reason: Beyond what this program can change.
effects:
  - id: eff-diabetes
    statement: Diabetes and heart disease
  - id: eff-costs
    statement: Higher health costs
    parents: [eff-diabetes]
outcomes:
  - id: oc-time-to-cook
    title: People have time to cook
    level: short_term
    addressesCauses: [cause-busy]
  - id: oc-eat-well
    title: People eat well
    level: impact
    addressesEffects: [eff-diabetes]

With element commands:

impact-model cause add impact-model.yaml --id cause-fast-food \
  --statement "Fast food is everywhere" --level underlying --parents cause-busy
impact-model effect add impact-model.yaml --id eff-costs \
  --statement "Higher health costs" --parents eff-diabetes
impact-model outcome set impact-model.yaml oc-eat-well --relieves eff-diabetes

Causes and effects form a network

A cause can contribute to several others, and an effect can follow from several others, so parents is a list. A tree is the case of at most one parent each. Validation rejects a cause or effect that is its own parent, and a chain of parents that leads back to where it started through any branch.

A cause's parents point from the deeper cause toward the problem: structural causes feed underlying ones, underlying causes feed immediate ones, and immediate causes lead to the core problem. Written the other way round, the tree draws upside down, so IMP-CAU-003 reports a cause whose parent sits at a deeper level.

How the diagram is laid out

The arrows flow upward, from the roots to the core problem and on to the branches:

  • The core problem is in the middle row.
  • A cause with no parents is in the first row below it. Any other cause is one row below its lowest parent, so every arrow from a cause points up.
  • An effect with no parents is in the first row above the core problem. Any other effect is one row above its highest parent.
  • Within a row, boxes keep the order of the model file.
  • A cause or effect set aside is drawn faded and labelled "(set aside)". A rejected one is left out.

The diagram is built first as a JSON document, a d2vision diagram spec with a zone for the effects, the core problem, and the causes, a row for each layer, and the edges between boxes. problem-tree-json prints that document, and D2 and Mermaid are both rendered from it, so another tool can read or restyle the same tree. D2 keeps every row in place; Mermaid draws bottom to top and places the rows itself.

The tables

Below the diagram, problem-tree and problem-tree-d2 list:

  • each cause with its level, what it leads to, and the outcomes that address it
  • each effect with what it follows from and the outcomes that relieve it

A dash means nothing addresses it yet, and "set aside" gives the reason.

What lint checks

Rule Checks
IMP-CAU-001 Each underlying and structural cause is addressed by an outcome or set aside
IMP-CAU-002 Outcomes address some root cause, not only immediate ones
IMP-CAU-003 A cause contributes only to causes at its own level or nearer the problem
IMP-EFF-001 Each effect is relieved by an outcome or set aside
IMP-EFF-002 Effects are relieved by long-term or impact outcomes, not only earlier ones

Known limitations

  • Labels are not wrapped, so a long problem statement makes a wide box. Keep the core problem to a short phrase and put the detail in the context's narrative.
  • Within a row, boxes keep file order, so arrows can cross. Reordering causes or effects in the file reorders the row.