> ## Documentation Index
> Fetch the complete documentation index at: https://docs.samplehc.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Column Values

> Adding custom column displays to workflow steps

## Overview

Column values display custom data in workflow tables, showing information like patient names, file names, or other key data extracted from the workflow context. They can be defined at the workflow level (all steps) or step level (overrides).

## Basic Usage

### Workflow-Level Columns

Define columns that appear for all steps using the `Workflow` constructor:

```python theme={null}
from workflows_py.workflow import Step, ScreenStep, Workflow

workflow = Workflow(
    get_display_col_values={
        "Patient ID": lambda ctx: ctx.get_start_data().get("patientId", ""),
        "Started By": lambda ctx: ctx.current_user["name"] if ctx.current_user else "Unknown",
    }
)
```

### Step-Level Columns (Overrides)

Individual steps can override or add columns:

```python theme={null}
workflow.then(
    Step(
        "process-documents",
        process_documents_function,
        get_display_col_values={
            "File Name": lambda ctx: ctx.get_step_result("document-upload")["documents"][0]["fileName"],
            "Patient ID": lambda ctx: ctx.get_start_data().get("patientId", "N/A"),  # Overrides workflow-level
        }
    )
)
```

## Column Value Structure

The `get_display_col_values` parameter is a dictionary where:

* **Keys**: Column names displayed in the interface
* **Values**: Lambda functions that receive `WorkflowRunContext` and return a column value or `None`
* **`None` values**: Display as "—" (em dash)

### Typed Column Values

Column functions can return more than strings. The return value determines the
column's type, which affects how the value is displayed and sorted:

| Return type         | Column type                 |
| ------------------- | --------------------------- |
| `str`               | string                      |
| `int`, `float`      | number                      |
| `bool`              | boolean                     |
| `datetime.date`     | date                        |
| `datetime.datetime` | datetime (converted to UTC) |

```python theme={null}
from datetime import date, datetime

get_display_col_values={
    "Patient ID": lambda ctx: ctx.get_start_data().get("patientId", ""),
    "Document Count": lambda ctx: len(ctx.get_start_data().get("documents", [])),
    "Is Urgent": lambda ctx: ctx.get_start_data().get("priority") == "urgent",
    "Received At": lambda ctx: datetime.fromisoformat(ctx.get_start_data()["receivedAt"]),
}
```

<Tip>
  Return `date`/`datetime` objects (rather than pre-formatted strings) for date
  columns so they sort chronologically.
</Tip>

If a column function raises an exception, the value is treated as missing —
the workflow run itself is not affected.

## Data Sources

### Start Data

```python theme={null}
"Patient ID": lambda ctx: ctx.get_start_data().get("patientId", ""),
"Priority": lambda ctx: ctx.get_start_data().get("priority", "Normal"),
```

### Step Results

```python theme={null}
"File Name": lambda ctx: ctx.get_step_result("document-upload")["documents"][0]["fileName"],
"Doc Count": lambda ctx: str(len(ctx.get_step_result("document-upload")["documents"])),
```

### User Information

```python theme={null}
"Started By": lambda ctx: ctx.current_user["name"] if ctx.current_user else "Unknown",
"User Email": lambda ctx: ctx.current_user["email"] if ctx.current_user else "",
```

## Safe Data Access

Always handle missing data gracefully:

```python theme={null}
get_display_col_values={
    # Safe access with defaults
    "Patient Name": lambda ctx: ctx.get_start_data().get("patientName", "N/A"),
    
    # Safe nested access
    "File Name": lambda ctx: (
        ctx.get_step_result("upload")["documents"][0]["fileName"]
        if ctx.get_step_result("upload") 
        and "documents" in ctx.get_step_result("upload")
        and len(ctx.get_step_result("upload")["documents"]) > 0
        else "No file"
    ),
    
    # Safe string formatting
    "Full Name": lambda ctx: (
        f"{ctx.get_start_data().get('firstName', '')} {ctx.get_start_data().get('lastName', '')}"
        if ctx.get_start_data().get('firstName') and ctx.get_start_data().get('lastName')
        else ""
    ).strip(),
}
```

## Best Practices

1. **Use descriptive column names**: `"Patient ID"` not `"ID"`
2. **Provide fallback values**: Always use `.get()` with defaults
3. **Format data appropriately**: Truncate dates, format numbers
4. **Keep lambdas simple**: Move complex logic to separate functions

```python theme={null}
def get_patient_display_name(ctx):
    first = ctx.get_start_data().get('firstName', '')
    last = ctx.get_start_data().get('lastName', '')
    if first and last:
        return f"{first[:1]}. {last}"
    return first or last or "Unknown"

get_display_col_values={
    "Patient": get_patient_display_name,
}
```

## Column Inheritance

Column ordering follows a specific pattern based on the order columns are defined in the dictionary:

### Ordering Rules

1. **Workflow-level columns** appear first in the order they were defined
2. **Step-level columns** with the same key/name **override** workflow-level columns but maintain their original position
3. **Step-level columns** with different keys are **appended** to the end in the order they were defined

### Example

```python theme={null}
workflow = Workflow(
    get_display_col_values={
        "Status": lambda ctx: "Default",
        "Patient ID": lambda ctx: ctx.get_start_data().get("patientId", ""),
        "Started By": lambda ctx: ctx.current_user["name"] if ctx.current_user else "Unknown",
    }
)

workflow.then(
    Step(
        "process-step",
        process_function,
        get_display_col_values={
            "Status": lambda ctx: "Processing",  # Overrides workflow "Status" at position 1
            "File Name": lambda ctx: "document.pdf",  # New column, appended at the end
            "Patient ID": lambda ctx: ctx.get_start_data().get("patientId", "N/A"),  # Overrides at position 2
        }
    )
)
# Final column order: Status (overridden), Patient ID (overridden), Started By (inherited), File Name (appended)
```

This ensures consistent column ordering across all workflow steps while allowing step-specific customization.

## Complete Example

```python theme={null}
from workflows_py.workflow import ScreenStep, Step, Workflow

def process_patient_data(ctx):
    return {"processed": True}

workflow = Workflow(
    get_display_col_values={
        "Patient ID": lambda ctx: ctx.get_start_data().get("patientId", ""),
        "Patient Name": lambda ctx: (
            f"{ctx.get_start_data().get('patientFirstName', '')[:1]} {ctx.get_start_data().get('patientLastName', '')}"
            if ctx.get_start_data().get("patientFirstName") and ctx.get_start_data().get("patientLastName")
            else ""
        ).strip(),
        "Started By": lambda ctx: ctx.current_user["name"] if ctx.current_user else "Unknown",
    }
)

workflow.then(
    ScreenStep(
        "document-upload",
        screen_path="./screens/document-upload-screen.tsx"
    )
).then(
    Step(
        "process-patient-data", 
        process_patient_data,
        get_display_col_values={
            "File Name": lambda ctx: ctx.get_step_result("document-upload")["documents"][0]["fileName"],
            "Status": lambda ctx: "Processing" if ctx.get_step_result("process-patient-data")["processed"] else "Pending",
        }
    )
)
```
