Syntax
Francis formulas follow the pattern"ROW_NAME"[RELATIVE_PERIOD]. The period offset is always relative to the current cell:
[0]: current period[-1]: previous period[-12]: 12 months ago
Sign conventions
Income and balance sheet items are entered as positive values. Expenses are entered as negative values. This is how Francis presents actuals from your accounting system, so follow this convention to compare apples to apples. The practical effect: subtotals add rather than subtract. Gross profit looks like this:Fill right
Write the formula logic once, then fill right to apply it to every future period. You don’t need to repeat the formula cell by cell. The same formula carries forward, and because references are relative, each period resolves against its own neighbors. It’s a small mechanic that most users come to rely on heavily once it’s part of their workflow. Fill right applies to rows. A calculation already uses one formula across every period by definition, so there is nothing to fill right. Override individual cells whenever you need to. To make a step change at a point in time, write a new formula in that period and fill right again. Everything from that period forward picks up the new logic, and the periods before it keep the old. Fill right works within the layer you’re in. Fill right in the actuals layer and the formula applies to all future periods of the actuals layer, so when you close a new month the formula is already in place. Fill right in the forecast layer and it applies to future forecast periods. To run the same formula across both layers, enter it in each layer and fill right in both. In the actuals layer, start from far enough back, because fill right only carries forward, as the name says. Begin at the earliest period you want the formula to cover, otherwise the periods to its left stay untouched.Overriding imported actuals
Actuals cells hold values from your mapped data sources, but they are still cells you can write to. Write a value or a formula into an actuals cell and it replaces the imported value for that month. Francis keeps what you wrote, so the cell shows your number rather than the one your accounting system holds. Override deliberately, not as a habit. The legitimate cases are narrow: an accrual your accounting system hasn’t booked yet, a reclassification you don’t want to make in the ledger, or a known posting error you’re correcting ahead of the fix. Add a comment explaining why, because an override is otherwise invisible to whoever reads the number six months from now. Overridden cells carry a yellow corner indicator. That marker is the only thing separating a value you typed from a value Francis imported, so scan for it first whenever your model doesn’t tie to your accounting system. Two consequences matter:- The override survives every sync. Syncing imports new journal entries, but it does not reclaim a cell you’ve written to. Entries booked to the mapped accounts after you set the override change nothing in the model until you remove it. Inspect keeps listing those entries, because it reads the accounts mapped to the row rather than the value in the cell. An override is the usual explanation when an entry is visible in Inspect but the row total ignores it.
- Everything downstream reads the override. Groups, calculations, and any formula referencing the row all take the overridden value, not the imported one. A single override propagates up the statement, so a small correction in one cell can move a subtotal you weren’t looking at.
Formula paths
A row name alone is enough when it is unique in the model. When the same row name appears in multiple sheets, groups, or breakdowns, Francis qualifies the reference with a formula path. The path keeps every cell reference unique even when row names repeat across entities, dimensions, or sections. Two separators can appear in the path:- Sheet (
!): the sheet name sits before!when the row lives on a different sheet. - Container (
.): a group or section name sits before.when the row lives inside a nested container.
"Cost VAT"[-1]: the"Cost VAT"row on the current sheet, previous period."VAT"!"VAT calculations"."Cost VAT addition"[0]: the"Cost VAT addition"row inside the"VAT calculations"group on the"VAT"sheet, current period.
Comments
Add# at the end of any formula to insert a comment. Everything after # is highlighted and ignored by the calculation.
Functions
Francis supports a set of built-in functions (avg(), sum(), if(), and others) for more complex logic.
avg("row_name"[range_start:range_end])
avg("row_name"[range_start:range_end])
avg_ytd("row_name")
avg_ytd("row_name")
avg_last_year("row_name")
avg_last_year("row_name")
avg_last("row_name", num_months)
avg_last("row_name", num_months)
median("row_name"[range_start:range_end])
median("row_name"[range_start:range_end])
median_ytd("row_name")
median_ytd("row_name")
median_last_year("row_name")
median_last_year("row_name")
median_last("row_name", num_months)
median_last("row_name", num_months)
sum("row_name"[range_start:range_end])
sum("row_name"[range_start:range_end])
sum_ytd("row_name")
sum_ytd("row_name")
sum_last_year("row_name")
sum_last_year("row_name")
sum_last("row_name", num_months)
sum_last("row_name", num_months)
receivables("row_name", payment_days)
receivables("row_name", payment_days)
payment_days argument must be hardcoded and cannot reference a row.ignore_div_zero(a/b)
ignore_div_zero(a/b)
#DIV/0 error. Use for margin and percentage calculations where the denominator may occasionally be zero.power(num, exponent)
power(num, exponent)
min(a, b)
min(a, b)
max(a, b)
max(a, b)
abs(num)
abs(num)
round(num, decimals)
round(num, decimals)
round_down(num)
round_down(num)
round_up(num)
round_up(num)
if(condition, value_if_true, value_if_false)
if(condition, value_if_true, value_if_false)
if_month(a, b, ...)
if_month(a, b, ...)
1 if the current period matches any of the specified month numbers, else 0.if_month(1)returns1in Januaryif_month(1, 6)returns1in January or Juneif_month(1, 6, 12)returns1in January, June, or December
if_quarter_month(num)
if_quarter_month(num)
1 if the current period is the nth month within its quarter.if_quarter_month(1)returns1in January, April, July, Octoberif_quarter_month(2)returns1in February, May, August, Novemberif_quarter_month(3)returns1in March, June, September, December
weekdays()
weekdays()
Errors
#DIV/0
#DIV/0
ignore_div_zero() to return zero instead.#NO_FRAC
#NO_FRAC
a/b).#ERR
#ERR
5+ throws #ERR because the expression is incomplete.#INF_LOOP
#INF_LOOP
#CIRCULAR
#CIRCULAR
#FUTURE_REF
#FUTURE_REF
[0] throws this error. "Revenue"[1] is invalid; "Revenue"[0] and "Revenue"[-1] are valid.#INVALID_REF
#INVALID_REF
#BAD_ARG
#BAD_ARG
#BAD_TYPE
#BAD_TYPE
#REF_ERR
#REF_ERR