Metadata-Version: 2.4
Name: consort-yaml
Version: 0.1.2
Summary: Generate a CONSORT flowchart from YAML.
License-File: LICENSE
Requires-Python: >=3.11
Requires-Dist: pyyaml>=6.0.3
Description-Content-Type: text/markdown

# Automatic CONSORT chart generation from YAML

Generate CONSORT-style flowcharts as [Mermaid](https://mermaid.js.org/) diagrams from YAML definitions.

## Installation

```bash
uv add consort-yaml
```

## CLI usage

```bash
uv run consort-yaml my_chart.yaml > chart.mermaid
mmdc -i chart.mermaid -o chart.svg
```

Or as a one liner:

```bash
uv run consort-yaml my_chart.yaml > chart.mermaid && mmdc -i chart.mermaid -o chart.svg
```

## Python API

```python
from consort_yaml import FlowchartBuilder, load_yaml

data = load_yaml("my_chart.yaml")
builder = FlowchartBuilder()
mermaid_str = builder.build(data)
print(mermaid_str)
```

## YAML format

A flowchart is defined by a top-level `n` (initial sample count) and a list of `steps`:

```yaml
---
n: 611
steps:
  - name: "Cohort"
  - name: "Processing"
    exclusions:
      - reason: "Failed QC"
        n: 5
  - name: "Analysis"
```

### Steps

Each step is a dict with the following keys:

- **`name`** (str, required): The label displayed in the flowchart node.
- **`id`** (str, optional): A custom node ID. If provided, this overrides
  the auto-generated ID (`step0`, `step1`, etc.). Useful for referencing
  nodes in `additional_links`.
- **`exclusions`** (list, optional): A list of exclusion dicts, each with:
  - **`reason`** (str): The exclusion reason.
  - **`n`** (int): The number of samples excluded.
  - **`id`** (str, optional): A custom node ID for the exclusion node.
  - **`color`** (str, optional): A custom fill color for this exclusion
    node (e.g. `#ffcccc`). Overrides the default exclusion color for
    this node only.
- **`subgraph`** (dict, optional): Renders the step as a subgraph containing
  nested steps. Contains:
  - **`direction`** (str, optional): Layout direction (`TD`, `LR`, `TB`, `RL`).
    Defaults to `TD`.
  - **`steps`** (list): Sub-steps (same structure as top-level steps).
- **`link`** (str, optional): Controls how the step connects to the previous
  step. Defaults to `"default"` (standard arrow). Special values:
  - `"none"`: The step is not connected to the previous step. Its `n` is
    computed from its own exclusions, but the running `n` is not decremented
    for subsequent steps.
  - Any other string (e.g. `"---"`): Used as the arrow style.

### Exclusion colors

The default fill color for exclusion nodes is `#ffdada` (light red). This can
be changed globally or per-exclusion:

- **Top-level `exclusion_color`** (str, optional): Sets the default fill
  color for all exclusion nodes.
- **Per-exclusion `color`** (str, optional): Overrides the fill color for a
  single exclusion node.

```yaml
---
n: 300
exclusion_color: "#ffcccc"
steps:
  - name: "Processing"
    exclusions:
      - reason: "Failed QC"
        n: 10
      - reason: "Missing data"
        n: 5
        color: "#ff9999"
```

In this example, all exclusions use `#ffcccc` except "Missing data" which
uses `#ff9999`.

### Additional links

A top-level `additional_links` key can be used to add arbitrary connections
between nodes. This is a list of strings in the format `"id1 link id2"`,
where `link` is any Mermaid arrow style (e.g. `--->`, `---`, `-.->`).

```yaml
---
n: 300
steps:
  - name: "Step A"
    id: custom_a
  - name: "Step B"
    id: custom_b
additional_links:
  - "custom_a -.-> custom_b"
```

This is useful for adding cross-references or dashed links between nodes that
are not adjacent in the flow.

### Example with subgraphs and `link: none`

```yaml
---
n: 300
steps:
  - name: "Analysis"
    subgraph:
      direction: TB
      steps:
        - name: "Gene1 prediction"
          link: none
          exclusions:
            - reason: "No mutation status"
              n: 60
        - name: "Gene2 prediction"
          link: none
          exclusions:
            - reason: "No mutation status"
              n: 60
```

In this example, both "Gene1 prediction" and "Gene2 prediction" start from `n=300`
and subtract their own exclusions independently, because `link: none` prevents
the exclusions from affecting the running sample count.

### Real example

Using the YAML file in `example` (`example/example-consort.yaml`):

```yaml
---
n: 300
steps:
  - name: "Histopathology dataset"
  - name: "Run Classpose"
    exclusions:
    - reason: Samples have poor quality
      n: 25
    - reason: Samples have no MPP information
      n: 10
  - name: "Feature extraction"
  - name: "Feature summarisation"
  - name: "Model training"
    subgraph:
      direction: TB
      steps:
        - name: "<i>Gene1</i>mut prediction"
          link: none
          exclusions:
            - reason: <i>Gene1</i> mutation status not available
              n: 25
        - name: "<i>Gene2</i>mut prediction"
          link: none
        - name: "<i>Gene3</i>mut prediction"
          link: none
```

We can run `uv run consort-yaml example/example-consort.yaml > example/example-consort.mmd` to get the Mermaid diagram as output:

```
---
config:
    theme: base
    themeVariables:
        fontFamily: helvetica
    flowchart:
        rankSpacing: 15
        nodeSpacing: 15
        subGraphTitleMargin:
            top: 10
            bottom: 10
            left: 0
            right: 0
---
flowchart TD
    classDef exclusion fill:#ffdada,stroke-width:1,stroke:black
    classDef step fill:white,stroke-width:1,stroke:black
    classDef sg fill:transparent,stroke-width:1,stroke:black


    step0["Histopathology dataset<br>(n=300)"]
    exclusion0["Samples have poor quality<br>(n=25)"]
    exclusion1["Samples have no MPP information<br>(n=10)"]
    step1["Run Classpose<br>(n=265)"]
    step2["Feature extraction<br>(n=265)"]
    step3["Feature summarisation<br>(n=265)"]
    subgraph sg0 [Model training]
        direction TB
        exclusion2["<i>Gene1</i> mutation status not available<br>(n=25)"]
        step4["<i>Gene1</i>mut prediction<br>(n=240)"]
        exclusion2 --> step4
        step5["<i>Gene2</i>mut prediction<br>(n=265)"]
        step6["<i>Gene3</i>mut prediction<br>(n=265)"]
    end
    step0 ---- exclusion0 --- exclusion1 ---> step1 ---> step2 ---> step3 ---> sg0
    class step0,step1,step2,step3,step4,step5,step6 step
    class exclusion0,exclusion1,exclusion2 exclusion
    class sg0 sg
```

And then convert this to PNG using `mmdc -i example/example-consort.mmd -o example/example-consort.png -s 4`:

![Example CONSORT diagram](example/example-consort.png)

Running everything as a single line:

```bash
uv run consort-yaml example/example-consort.yaml > example/example-consort.mmd && mmdc -i example/example-consort.mmd -o example/example-consort.png -s 4
```
