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

# Calculated fields

> Build dependable derived values from record data and relations, then test persistence, refresh behavior, and data-quality limits.

A calculated field stores a value that KayanOS derives from other data instead of asking a person to type it repeatedly. It can express an amount, score, flag, label fragment, relationship count, or other result that follows an approved rule. The result stays on the record so it can be reviewed, filtered, reported on, and used by later steps without redoing the calculation by hand.

Calculated field is not a separate item in the entity field-type list. It is a normal, non-action, non-status field with a non-empty `calculation_code` expression. Choose the output field type to match the result: use Number for an amount or count, Boolean for a clear true/false result, Text for a short displayed value, and so on. A serial reference should use its dedicated serial configuration rather than a calculation that imitates issuance.

The captured builder view below shows the **Estimated Fee** number field on the **Citizen Service Request** entity with its calculation expression. It is an example of selecting the ordinary output type first, then attaching the calculation rule to that field.

![KayanOS calculated Estimated Fee field configuration for the Citizen Service Request entity.](https://kayanos.app/docs-images/en/build/calculated-fields.png)

## When to use it

Use a calculated field when the result is deterministic from data that the record already holds or is configured to resolve. Good candidates include a service fee from an amount and rate, a completeness flag from structured intake data, an inspection count, an elapsed-workload score, or a reporting category derived from approved inputs.

Use a different mechanism when the work needs judgment, authority, or an external event:

| Need                               | Better approach                               | Why                                                                         |
| ---------------------------------- | --------------------------------------------- | --------------------------------------------------------------------------- |
| A reviewer must decide an outcome  | Status, action, and accountable review fields | A formula cannot stand in for a decision.                                   |
| A user must enter an explanation   | A normal editable field                       | A calculation should not erase or guess human narrative.                    |
| An issued public number            | Serial ID                                     | Issuance has a sequence, collision protection, and change history.          |
| A rule that should block bad input | Field validation                              | A calculated result can show a problem, but validation controls acceptance. |
| A value from an unrelated service  | An approved integration or action             | A calculation only sees the context supplied to it.                         |

Keep inputs visible and named for their business meaning. A field named `requested_amount` and another named `fee_rate` make the rule reviewable; one opaque JSON payload does not. Do not make people enter a value that the calculation immediately overwrites. If the source data is incomplete, decide whether the result should be `null`, whether a validation should stop the record, or whether a human must provide a separate decision.

## Configuration

Choose the output field, then add its calculation expression in the entity builder. The calculation editor validates the expression against the context it provides and shows an error or warning state while you work. That check is useful for misspelled keys and invalid syntax, but it is not proof that live relationship data, permissions, or a later background refresh will behave as expected. Save and test a real non-production record.

### Context and output behavior

The evaluation context depends on the execution path. Do not assume that a name available while a record is created or updated is also available to the background refresh. The two paths have deliberately different data:

| Context value       | Create/update validation and persistence                                                                                                                                     | Background `calculate_entity_fields` refresh                                                                                                                                 |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `$`                 | A copy of the record data with only the output field currently being evaluated removed. Other calculated outputs can therefore be present during that synchronous operation. | A copy of stored record data with **all** calculated-field keys removed. A background formula must derive its result from source data rather than another calculated output. |
| `$value`            | The current value of the output field at the time that field is evaluated. Use it only when a prior value is part of the approved rule.                                      | Not supplied.                                                                                                                                                                |
| `$relations`        | Direct relation data only when the surrounding create/update flow has resolved it. A single relation is an object or `null`; an array relation is an array of objects.       | Direct relation data only for relationship fields configured for calculated-field refresh.                                                                                   |
| `$inverseRelations` | Always an empty object; synchronous calculation does not receive inverse-record collections.                                                                                 | Configured inverse relationship collections, represented as arrays under the inverse entity and field keys.                                                                  |
| `$statusTemplates`  | Server-supplied status-template context where the relevant configuration applies.                                                                                            | Not supplied.                                                                                                                                                                |

In the synchronous path, KayanOS may make up to one pass per calculated field and stops when a pass makes no change. The expression result is compared with the current value; when it differs and is defined, the updated value is placed into the record data used by that create/update flow. A result of `undefined`, or an evaluation error, is skipped rather than written as a new value. The background job takes a different approach: it evaluates each calculated field once from its source-only context, then writes changed results together. That makes defensive null handling important: a silent blank may be safer than a wrong amount, but it can leave an earlier result in place until the source data is corrected.

Start with a local calculation that has no relationship dependency:

```jexl theme={null}
$.requested_amount * $.fee_rate
```

With `requested_amount` equal to `250000` and `fee_rate` equal to `0.03`, the expected Number result is `7500`. Both source fields should be Number fields. If a rate is optional, explicitly choose a fallback rather than accidentally multiplying text, `null`, or a missing value.

### Direct relations and inverse relations

A direct-relation formula only sees `$relations` in a normal create or update when the surrounding validation has resolved that relation context. The background job resolves only the direct relationship fields configured for calculated-field refresh. For a single relation such as `service_category`, `$relations.service_category` is either the related record object or `null`. For an array relation, the same key resolves to an array. Protect a relation access with a guard:

```jexl theme={null}
$relations.service_category && $relations.service_category.fee_rate != null
  ? $.requested_amount * $relations.service_category.fee_rate
  : null
```

With a related category rate of `0.03` and requested amount `250000`, the expected result is `7500`; without a category or rate, the expected result is `null`. Use the stored field keys of the related entity, not labels shown to staff.

Inverse relations answer a different question: which other records point back to this record? They are **background-path data**, not a guaranteed synchronous context: during normal create and update, `$inverseRelations` is empty. Configure the inverse relationship on the entity before using it, then use the expression only where the configured background refresh is part of the design. In that background path, the values are arrays under the inverse entity key and its relation field key. Use the registered `length(...)` helper to count the array:

```jexl theme={null}
length($inverseRelations.inspections.request_reference)
```

If three inspection records point to the current request through `request_reference`, the expected Number result is `3` after the configured background recalculation. The keys `inspections` and `request_reference` in this example must match the configured inverse entity and field keys exactly. Do not write an aggregate against an inverse path until you have verified the saved configuration and refreshed parent record.

There are two important timing paths. During create and ordinary update, KayanOS evaluates local data, any resolved direct relations, `$value`, and applicable status-template context; it has no inverse-record collection. The background `calculate_entity_fields` job reads configured direct and inverse relations, omits `$value` and `$statusTemplates`, recomputes each output once from source data, and logs evaluation errors. A rule that relies on inverse records should be verified after the background recalculation; do not assume that adding a child record instantly refreshes the parent’s inverse count in every path.

### Dependencies, cycles, and status context

A short calculated-on-calculated chain can appear to settle during one synchronous create or update: that path may make repeated passes, bounded by the number of calculated fields. It is not a production-safe dependency contract. The background refresh removes every calculated output from `$`, makes one pass, and can later overwrite values from source data alone. Build durable policy from approved source fields instead of relying on one calculated result being available to another. Do not create circular rules such as `a` depending on `b` while `b` depends on `a`, or formulas that keep changing their own inputs.

Status-template information can be supplied by the server during the synchronous path when a field uses the relevant status-template configuration. It is not supplied to the background job. Treat it as contextual data, not as an authorization shortcut; if a rule depends on a status template, test the exact template selection, record state, and refresh behavior that will be used at runtime.

### Client projection is not the calculation authority

KayanOS can omit calculated values and calculated table-column metadata from client payloads, then recompute authoritative outputs on trusted server paths. This prevents a stale or user-modified client projection from becoming the source of a saved calculated result. It also means a calculated value that is absent from the browser payload is not necessarily missing from the record's server-side calculation contract.

Do not use a hidden calculated value for client-side authorization, page visibility that must be enforced securely, or approval evidence. Base those decisions on permitted source fields and server-validated workflow state. When a calculated table result matters to a document or decision, verify the saved or recomputed server value and the inputs that produced it rather than copying a value from browser state.

## Worked example

A public-service directorate in Syria manages citizen requests for a local service. Each request includes `requested_amount`, `fee_rate`, a relation to `service_category`, and related inspection records. The service owner wants staff to see the estimated fee, a review flag, and the number of completed inspection records without retyping any result.

Create these output fields:

| Output field       | Type    | Calculation purpose                                                           |
| ------------------ | ------- | ----------------------------------------------------------------------------- |
| `estimated_fee`    | Number  | Amount multiplied by the applicable rate.                                     |
| `needs_fee_review` | Boolean | Signals that the estimated fee reaches an agreed review threshold.            |
| `inspection_count` | Number  | Shows the configured inverse inspection count after background recalculation. |

For the first version, keep the rate directly on the request and use a simple calculation:

```jexl theme={null}
$.requested_amount * $.fee_rate
```

For a request with `requested_amount` of `250000` and `fee_rate` of `0.03`, `estimated_fee` becomes `7500`. Place the result beside the source amount in the review layout, and keep both inputs visible so a reviewer can explain the number.

Then define a clear Boolean threshold directly from the same source values:

```jexl theme={null}
$.requested_amount * $.fee_rate >= 10000
```

For the example above, `needs_fee_review` is `false`; when the amount and rate produce a fee of `12500`, it is `true`. This is deliberately not `$.estimated_fee >= 10000`: a synchronously calculated `estimated_fee` can be visible during one validation pass, but the background path removes calculated outputs before it evaluates the threshold. Configure the business process so a true flag leads to a visible review task or status step. The field itself does not assign a reviewer or grant approval.

If fee policy is stored on the related `service_category` record, replace the first formula with the guarded direct-relation form from the Configuration section. Test a request with no category, a category with no rate, and a category with a valid Number rate. If the relationship changes later, run the relevant recalculation check and verify the stored fee before producing a decision document.

Finally, configure the inverse `inspections.request_reference` relationship only if the directorate needs the stored count for dashboards or reporting. Create one request, attach three inspection records, run or wait for the configured background recalculation, and confirm `inspection_count` is `3`. A count is operational information; it is not proof that each inspection was accepted. Keep the inspection result and decision status in their own controlled fields.

## Testing

Test calculations with a small non-production set that includes normal, empty, changed, and relationship cases. Write down both source values and expected stored results. Test a saved record, not only the editor indicator, because the editor can validate syntax while live data has a different relationship shape or missing value.

| Test                              | Setup                                                                         | Expected result                                                                                                                                |
| --------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| Local arithmetic                  | Known Number inputs                                                           | The stored output equals the independently calculated amount.                                                                                  |
| Missing input                     | Remove one optional source value                                              | The formula returns the approved fallback such as `null`; it does not create a misleading number.                                              |
| Direct relation                   | Use a valid related record, then remove it                                    | The guard produces the related result when present and the fallback when absent.                                                               |
| Inverse count                     | Add and remove related child records                                          | The background-recalculated output matches the configured array length.                                                                        |
| Background-safe threshold         | Change `requested_amount` or `fee_rate`, then run or wait for refresh         | The flag equals the direct source expression in both paths; it does not require another calculated output.                                     |
| Synchronous dependency experiment | Change a source for two calculated fields in one ordinary update              | A short chain can settle within bounded synchronous passes, but it is not accepted as proof that a background refresh will preserve the chain. |
| Output editing                    | Try to supply a conflicting value for the output in a normal create or update | That synchronous calculation uses its evaluated result; verify the eventual stored value again after any background refresh.                   |
| Error handling                    | Temporarily use an invalid key or incompatible data                           | The editor flags the issue or evaluation is skipped; background logs identify the failed field.                                                |
| Access review                     | Test with a restricted role                                                   | The layout and calculation do not expose a sensitive source through a derived value.                                                           |

Use a conservative relation test expression before adding arithmetic:

```jexl theme={null}
$relations.service_category ? $relations.service_category.fee_rate : null
```

With a related category whose `fee_rate` is `0.03`, the expected result is `0.03`; with no related category, it is `null`. After this succeeds, add multiplication and threshold logic one expression at a time. For an inverse count, test the exact saved configuration rather than guessing the entity or field key from its screen title.

## Troubleshooting

| Symptom                                           | Check first                                                                                               | Safe response                                                                                                                                  |
| ------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| The calculated value is blank                     | Is a source key missing, is the result `undefined`, or did a guard intentionally return `null`?           | Inspect a saved test record and make the intended fallback explicit.                                                                           |
| The editor shows an error                         | Does the expression use the supplied context and valid JEXL syntax?                                       | Start with one source path, verify it, then add operators and guards.                                                                          |
| A relation value is missing                       | Is the field a direct relation, is it configured as one value or an array, and is that path resolving it? | Use the correct `$relations` shape and guard `null` or empty arrays.                                                                           |
| An inverse count is blank or stale                | Has the inverse relationship been configured, and has the background recalculation completed?             | Do not expect an inverse count during synchronous create/update; run or wait for background recalculation and verify the stored parent record. |
| A result does not change                          | Did the source value actually persist, or is the expression returning the same JSON-equivalent value?     | Check the saved source fields and field keys before changing the formula.                                                                      |
| A dependency gives different values after refresh | Does the expression read another calculated field, `$value`, or `$statusTemplates`?                       | Rewrite the durable rule from source fields, then test the background result.                                                                  |
| A calculation loops or gives unstable values      | Do calculated fields depend on one another in a cycle during synchronous passes?                          | Remove the cycle and make the durable calculation read source data only.                                                                       |
| A background job reports an error                 | Are a relation path, field key, or source type wrong?                                                     | Correct the data or expression, then rerun the calculation and inspect the stored result.                                                      |

## Permissions and data-quality limits

Only people with appropriate builder access should configure calculation code, relation definitions, and layouts. Members who create or update a record still need the normal entity permissions; a calculated field does not bypass field visibility, record scope, or update authorization. Treat a value produced in one synchronous pass as a result for that operation, not as a permission-approved guarantee that another calculated field will persist it after background refresh. Test with representative roles, especially where a result could reveal a restricted amount, person, category, or inspection outcome.

Derived data can leak source data. For example, a Boolean flag can reveal whether a confidential threshold was exceeded, and a count can reveal that related records exist. Do not expose a calculated field in a public or broad staff layout until the service owner has reviewed both the source fields and what the derived result reveals. Do not put secrets, tokens, or personal identifiers into expression text or output values.

Keep the formula small enough for a policy owner to read. Pair every meaningful calculation with a field description that says what it represents, its unit, its fallback for missing data, and the action staff should take when it is unusual. Calculations increase consistency; they do not validate an untrustworthy source, replace approval, guarantee a timely inverse refresh, or preserve a calculated-on-calculated dependency across a background pass.

## Related guides

* Select suitable output and source field types in [Entity fields and layouts](/build/entity-fields-and-layouts).
* Learn complete expression syntax and helper behavior in [Expression language syntax](/reference/expressions/language-syntax).
* Design direct and inverse relationships in [Entities](/build/entities).
* Use issued, auditable human references in [Serial IDs](/build/serial-ids).
