Table Calculations

AdviceDocs supports arithmetic and a small set of helper functions inside {{ ... }} placeholders. This is most useful in tables, where each row often needs a derived value (a line total, a percentage, a row that aggregates a column) and the data extraction step doesn’t pre-compute it. The renderer wraps numeric fields in a smart number type that handles addition, multiplication, division, and so on even when the underlying value arrives as a string.

What works inside {{ ... }}

Arithmetic

All the standard binary operators work: +, -, *,/, // (floor division), % (modulo), ** (exponent). String values that look like numbers are auto-converted, so you don’t have to worry about whether price arrived as "1234.50" or 1234.5.

Arithmetic in placeholders
{{ price * quantity }}
{{ amount + extra }}
{{ end_balance - start_balance }}
{{ total / count }}
{{ amount * 1.1 }}          (add 10%)
{{ amount * (1 - 0.075) }}  (subtract 7.5%)

Division by zero is safe

{{ value / 0 }} renders as 0. The smart number wrapper catches the divide and returns zero rather than failing the whole template render.

The sum() function

AdviceDocs exposes a custom sum() function that handles three patterns:

  • {{ sum(numbers) }} — sum a flat list of numbers.
  • {{ sum(items, 'attribute') }} — sum one attribute across a list of objects.
  • {{ sum(items, 'nested.attribute') }} — nested attribute paths work too.
sum() examples
{{ sum(asset_values) }}
{{ sum(client.investments, 'balance') }}
{{ sum(accounts, 'portfolio.value') }}
Total: {{ sum(products, 'value') }}

Other helper functions

  • len(items) — count the items in a list.
  • min(a, b), max(a, b) — smallest / largest of the arguments (or of a list).
  • round(x, n) — round to n decimal places.
  • abs(x) — absolute value.
  • int(x), float(x) — coerce a value into the matching numeric type.

Ternary expressions

Inline conditionals using Jinja2’s ternary syntax work:

Ternary in placeholders
{{ "Yes" if approved else "No" }}
{{ recommended_amount if has_recommendation else 0 }}
{{ price * 0.9 if is_member else price }}

Comparisons

You can write a comparison inside a placeholder; it renders as True or False. In practice, this is usually clearer wrapped in a ternary ({{ "Yes" if balance > 0 else "No" }}).

Putting calculations in tables

The most common use case is a per-row line total (e.g. value × weight) and a footer row with the column total. Use a regular table-row loop with arithmetic in the cells, then a separate row outside the loop with sum().

Line totals + column total
| Asset | Value | Weight | Allocation |
|-------|-------|--------|------------|
{% tr for asset in assets %}
| {{ asset.name }} | {{ asset.value }} | {{ asset.weight }} | {{ asset.value * asset.weight }} |
{% endtr %}
| **Total** | {{ sum(assets, 'value') }} | | {{ sum(assets, 'value') * 1.0 }} |
Percentage of total per row
{% tr for product in products %}
| {{ product.name }} | {{ product.value }} | {{ round(product.value / sum(products, 'value') * 100, 1) }}% |
{% endtr %}
Conditional cell
{% tr for account in accounts %}
| {{ account.name }} | {{ account.balance }} | {{ "High" if account.balance > 100000 else "Standard" }} |
{% endtr %}

Auto-formatting

Calculation results are automatically formatted with thousands separators. So {{ 1000000 }} renders as 1,000,000, and {{ price * quantity }} when the result is 123456.78 renders as 123,456.78.

Field values are NOT auto-formatted

Auto-formatting only kicks in for arithmetic results. A bare {{ account_balance }} renders the value as it arrived from the data layer — no commas, no currency symbol. To force formatting, multiply by 1 ({{ account_balance * 1 }}) or pre-format the value upstream.

What does not work

  • Filters{{ value | upper }}, {{ amount | round(2) }}, {{ items | length }} all silently break (the parser strips the filter and only the field name is extracted; the renderer either ignores the filter or fails). Use a function call instead: {{ round(amount, 2) }}, {{ len(items) }}.
  • String concatenation with +{{ "$" + amount }} doesn’t work because + is numeric. Put literal text outside the braces instead: ${{ amount }}.
  • Method calls{{ date.strftime("%Y") }}, {{ name.upper() }}. Pre-format on the data side.
  • Running totals inside a loop — AdviceDocs doesn’t support {% set %}, so you can’t accumulate a running sum across iterations. Use sum() in a footer row, or pre-compute the totals in extraction.
  • Date arithmetic{{ end_date - start_date }} doesn’t produce a meaningful timedelta. Compute the difference upstream and reference it as a numeric field.
  • Custom Python functions — only the helpers listed above are exposed. There’s no way to register your own.

When to pre-compute instead

In-template calculations are powerful but they don’t make it into the field list. If you want a value to appear in the field configuration UI — with a description, a manual type override, condition dependencies, or LLM-driven extraction — pre-compute it on the data side and reference it as a regular field.

Pre-computed total field
{{ total_assets }}                  (pre-computed in extraction)
{{ recommended_contribution }}      (pre-computed)
{{ projection_year_5_balance }}     (pre-computed)

Common pitfalls

Strings with leading zeros are converted to numbers

A field that arrives as "007" becomes 7 when used in arithmetic. Account numbers, postcodes, and other identifiers should be referenced without operators ({{ account_number }}) so they stay strings.

Filter syntax silently disappears

{{ amount | round(2) }} looks valid but doesn’t do what you expect: the filter is stripped at parse time and the renderer falls through. Use the function form {{ round(amount, 2) }} instead.

Use sum() over manual loops

Don’t try to accumulate totals via a plain {% for %}. Use {{ sum(items, 'value') }} in the footer row of the table — it evaluates against the same collection the loop iterates over.

Multiply by 1 to force number formatting

If you want commas on a raw field, the easiest trick is {{ field_name * 1 }}. The arithmetic wrapper kicks in and the result is auto-formatted.

Syntax to copy

Arithmetic
{{ price * quantity }}
{{ amount + extra }}
{{ total / count }}
{{ amount * 1.1 }}
{{ round(amount, 2) }}
sum() patterns
{{ sum(values) }}
{{ sum(items, 'value') }}
{{ sum(accounts, 'portfolio.balance') }}
Ternary
{{ "Yes" if approved else "No" }}
{{ price * 0.9 if is_member else price }}
{{ recommended_amount if has_recommendation else 0 }}
Forced formatting
{{ account_balance * 1 }}
{{ portfolio_value * 1 }}
Table with line totals + footer total
| Asset | Value | Weight | Line Total |
|-------|-------|--------|------------|
{% tr for asset in assets %}
| {{ asset.name }} | {{ asset.value }} | {{ asset.weight }} | {{ asset.value * asset.weight }} |
{% endtr %}
| **Total** | {{ sum(assets, 'value') }} | | |