# Set derived attributes

Write a key/value onto the invoice from the workflow: statically, via expressions, or with an LLM, then use that value as an approval-matrix column, conditional branch, or export field.

Use **Set derived attribute** to store a business label on the invoice (for example `business_line` = `Line-A` or `MOC`), then match that label as a column in your **booking-account approval matrix** so the right approvers are selected.

You choose how the value is produced under **Value source**:

- **Static value**: plain text or expression tokens (e.g. `Online`, `Invoice ${invoice_number}`)
- **LLM derived**: a short instruction; Cortena sends the invoice, line items, and booking match to an LLM and stores the returned value plus a reason

To **show** or **edit** derived fields on invoice detail and map them into Twinfield or Exact Online descriptions, see [Show and edit derived attributes on invoices](https://help.cortena.ai/guides/workflows/show-edit-derived-attributes).

**Who this is for:** Finance and workflow admins who need approval routing that depends on something Cortena does not extract as a fixed field, such as **business line**, channel, or entity segment, and who already use (or plan to use) an **approval matrix** spreadsheet. Also useful when the label is hard to express with a Conditional alone and an LLM can classify from supplier and booking context.

## What this feature does

**Set derived attribute** is a workflow **action** step. When an invoice reaches that step, Cortena writes one **attribute key** and **value** onto the invoice (for example `business_line` → `Line-A`).

Common patterns:

- **Conditional → Static set**: different branches write different fixed values, then join before a booking-account **Approval**
- **LLM set (no Conditional)**: one step classifies the attribute from the invoice context; optionally route on **Success** vs **Empty / error**

On the **Approval** step, you select which derived attributes participate in **matrix matching**. Cortena looks for matching columns on the matrix spreadsheet (for example **Business Line**) and uses the invoice value to narrow which rows, and therefore which approvers, apply.

## How it fits the approval matrix

| Piece | What you configure | What happens at runtime |
| --- | --- | --- |
| **Conditional** (optional) | Branches on supplier, PO, invoice text, amount, … | Invoice takes one path (e.g. Line-A vs Line-B). Skip if you use **LLM derived** instead |
| **Set derived attribute** | **Attribute key** · **Static value** or **LLM derived** instruction | Value is stored on the invoice (chip under the invoice number; optional field above line items) |
| **Approval** (booking account rules) | **Derived attributes for matrix matching**: select keys + short descriptions | Matrix rows are filtered by that column (exact match; empty cell = any value) |
| **Matrix spreadsheet** | Column such as **Business Line** with `Line-A` / `Line-B` / blank | Only matching rows remain for assignee resolution |

Related deep-dive: [Approval matrix](https://help.cortena.ai/guides/workflows/approval-matrix).

## LLM derived values

When **Value source** is **LLM derived**, Cortena does not use a fixed string. It sends your **Instruction for the LLM** together with the current invoice (header, line items, and booking match) and asks the model for:

- a **value** for the attribute key (for example `MOC` or `SOC`)
- a short **reason** explaining why

If the model returns a usable value, Cortena stores both. On invoice detail, when the attribute is shown above the line items, hover the field to read the LLM **reason** (same tooltip style as other invoice explanations).

### When the LLM returns nothing or fails

Under **When the LLM returns nothing or fails**, choose one policy:

| Option | What happens | Outputs on the canvas |
| --- | --- | --- |
| **Set an empty value and continue** | Stores an empty attribute and follows the normal single outgoing path | One outgoing connection (like Static) |
| **Take the Empty / error path** | Stores an empty attribute and routes via the **Empty / error** output | Two labeled outputs: **Success** and **Empty / error**. Connect both |

**Publish validation:** If you choose **Take the Empty / error path**, the workflow editor requires both **Success** and **Empty / error** edges before you can publish. Use **Empty / error** for a human review step, a fallback static set, or a different approval path.

### Example instruction

> Classify the business line as `MOC` or `SOC` based on the supplier and booking accounts. Return only one of those codes as the value.

Keep allowed values **exactly** aligned with your approval matrix column cells (casing included).

## Example: business line before approval

**Goal:** Invoices for supplier *Acme Online* should use business line **Online**; everything else uses **Retail**. Approvers in the matrix differ by **Business Line** as well as GL / amount tier.

You can do this with **Conditional + Static**, or with a single **LLM derived** step when the rule is harder to express as filters.

### Option A: Conditional + Static value

#### 1. Branch with a Conditional

1. Open **Workflow** → **Invoice workflow** (or the workflow you manage).
2. Add a **Conditional** step before your booking-account **Approval**.
3. Create two branches, for example:
   - **If** supplier name matches *Acme Online* (or invoice text / PO pattern you prefer)
   - **Else**: all other invoices

#### 2. Set the derived attribute on each branch

1. On the **If** branch, add **Set derived attribute** (under **Actions** in **ADD STEP**).
2. In the right-hand panel:
   - **Attribute key:** `business_line` (lowercase letters, digits, underscores; must start with a letter)
   - **Value source:** **Static value**
   - **Value:** `Online` (plain text is fine)
3. On the **Else** branch, add another **Set derived attribute** with the same key `business_line` and value `Retail`.
4. Connect both branches so they converge into your **Approval** step that uses the approval matrix.

### Option B: LLM derived (one step)

1. Add a single **Set derived attribute** before **Approval** (no Conditional required).
2. **Attribute key:** `business_line`
3. **Value source:** **LLM derived**
4. **Instruction for the LLM:** e.g. `Classify business line as Online if the supplier is Acme Online (or clearly online retail); otherwise Retail. Return only Online or Retail.`
5. Under **When the LLM returns nothing or fails**, pick **Set an empty value and continue**, or **Take the Empty / error path** and connect both outputs (e.g. Empty / error → a review step).
6. Connect **Success** (or the single outgoing path) into your matrix **Approval**.

**Values must match the sheet.** Matrix matching is an **exact** string match (after normal trimming). If the node writes `Online` but the sheet cell says `ONLINE` or has trailing spaces, you may not get the row you expect. Keep casing and spelling consistent for both Static and LLM outputs.

### 3. Enable the attribute on the Approval node

1. Select the **Approval** step that uses **booking account rules** / the matrix.
2. Find **Derived attributes for matrix matching**.
3. Tick **`business_line`**.
4. Add a **short description** for header analysis, for example: `Business line: Online, Retail`. That description helps Cortena detect the right spreadsheet column when analysing or refreshing the matrix schema.
5. If the list is empty, add at least one **Set derived attribute** step in **this** workflow first. Keys only appear when those steps exist on the canvas.

### 4. Put the column on the matrix spreadsheet

1. Open the managed approval matrix Google Sheet (see [Approval matrix](https://help.cortena.ai/guides/workflows/approval-matrix)).
2. Ensure there is a column for the attribute, typically **Business Line** for key `business_line`.
3. Fill cells with the same values you set in the workflow (`Online`, `Retail`). Leave a cell **blank** when that row should match **any** business line (wildcard).
4. Save / refresh the matrix schema if you added a new column so Cortena remaps headers.

### 5. What you see on an invoice

After the set step runs, the invoice detail shows a chip such as **`business_line: Online`** near the invoice number (and, if you enable **Show on invoice detail** / **Allow editing**, a full field above the line items: see [Show and edit derived attributes on invoices](https://help.cortena.ai/guides/workflows/show-edit-derived-attributes)). When the approval step runs, Cortena filters matrix rows using that value before resolving approvers.

## Configure the Set derived attribute node

1. Go to **Workflow** and open the editor for the tenant.
2. From **ADD STEP** → **Actions**, add **Set derived attribute**.
3. Set a clear **Label** (shown in the workflow strip / activity context).
4. Fill **Attribute key**.
5. Choose **Value source**:
   - **Static value**: fill **Value** (plain text and/or tokens)
   - **LLM derived**: fill **Instruction for the LLM** and pick a policy under **When the LLM returns nothing or fails**
6. Optionally enable **Show on invoice detail** / **Allow editing on invoice detail** (see [Show and edit derived attributes on invoices](https://help.cortena.ai/guides/workflows/show-edit-derived-attributes)).
7. Connect the step on the path where the value should be decided (often before matrix **Approval**). For LLM + **Take the Empty / error path**, connect both **Success** and **Empty / error**.
8. **Save** / publish the workflow.

### Attribute key rules

- Must match: start with a letter, then only lowercase letters, digits, or underscores
- Examples: `business_line`, `channel`, `cost_pool`
- Invalid keys (spaces, capitals, starting with a digit) cause the step to fail

### Static values: plain text or expressions

- **Plain text** is stored as-is (`Line-A`, `Online`).
- Optional **dollar-brace** expressions (click a variable in the panel to copy), for example `${invoice_number}` or `${purchase_order_number}`.
- You can also reference an attribute already set earlier: `${derived.business_line}`.
- To keep a literal `${…}` in the value, prefix the dollar sign with a backslash.
- If the resolved value is empty, the step **fails**. Avoid expressions that resolve blank for your invoices.

Supported tokens include: `invoice_number`, `purchase_order_number`, `invoice_supplier_name`, `invoice_recipient_company`, `invoice_recipient_name`, `currency`, and `derived.<key>`.

### LLM derived: instruction and empty/error policy

- Write a concrete instruction that names the allowed output values when those feed an approval matrix.
- Cortena includes invoice header, line items, and booking match with the instruction. You do not paste invoice data into the instruction yourself.
- **Set an empty value and continue**: one outgoing path; empty attribute is stored and the workflow continues.
- **Take the Empty / error path**: canvas shows **Success** and **Empty / error**; both must be connected before publish.

## Optional: branch later on a derived attribute

After a value is stored, a later **Conditional** can use the **Derived attribute** criterion (attribute key + allowed values) to route again, for example export path vs extra review, without re-deriving the label.

## Tips and limitations

- **Same workflow only**: Matrix checkboxes list keys from **Set derived attribute** steps on **this** workflow definition.
- **Descriptions matter**: Short, concrete descriptions improve column detection when Cortena analyses or refreshes the spreadsheet.
- **Empty matrix cell = wildcard** for that attribute; missing value on the invoice skips that filter dimension.
- **Overwrite**: Running another set step with the same key replaces the previous value (and LLM reason meta when the new write is LLM-derived).
- **LLM reason on invoice**: Hover a shown derived field to see why the LLM chose the value; editing the value on the invoice does not change the stored reason text.
- **Static empty fails; LLM empty is configurable**: A Static expression that resolves blank fails the step. LLM empty/failure follows your **When the LLM returns nothing or fails** policy.
- **Permissions**: Editing the workflow requires workflow management permission for the tenant.
- Prefer one clear key (e.g. `business_line`) over many overlapping labels unless the matrix truly needs multiple dimensions.

## Quick reference

| Task | Where |
| --- | --- |
| Add **Set derived attribute** | **Workflow** editor → **ADD STEP** → **Actions** |
| Choose Static vs LLM | Node panel → **Value source** tabs |
| LLM empty / error routing | Node panel → **When the LLM returns nothing or fails**; connect **Success** / **Empty / error** when required |
| Select keys for matrix matching | **Approval** node → **Derived attributes for matrix matching** |
| Fill matching column values | Approval matrix Google Sheet (e.g. **Business Line**) |
| See stored value on an invoice | Invoice detail: chip under the invoice number; optional field above line items |
| Read LLM reason | Hover the shown derived attribute field on invoice detail |
| Branch on a stored value | **Conditional** → criterion **Derived attribute** |
