Skip to content

Billing rates

Stundu's billing engine goes beyond a simple hours × rate formula. You can define flat hourly rates, after-hours multipliers, holiday multipliers, and fully custom expressions — all per tag.

Set a flat hourly rate

  1. Go to the Tags tab.
  2. Click Rate on any tag to open the rate modal.
  3. Go to the Rates tab.
  4. Enter an amount, a currency, and an effective-from date.
  5. Click Add.

The rate takes effect from the date you set. Earlier entries are billed at whatever rate was active at the time they were recorded.

Rate history

Every tag keeps a full history of rate changes. Stundu applies the correct rate to each entry based on when it was recorded — you never lose historical billing accuracy when you change a rate.

After-hours modifier

An after-hours modifier multiplies the base rate for entries recorded outside normal working hours.

  1. Open the rate modal for a tag.
  2. Go to the Modifiers tab.
  3. Set an after-hours multiplier and an effective-from date.
  4. Click Add.

Tip

A multiplier of 1.5 bills after-hours work at 150 % of the base rate.

Holiday modifier

A holiday modifier works the same way as after-hours, but applies on public holidays. Set it separately on the Modifiers tab.

Expression-based rates

For complex billing rules, Stundu supports MathJSON expressions. Expressions can be used in two places: as the hourly rate itself (rate expression), or as an after-hours / holiday modifier (modifier expression). Each context has its own set of available variables.

Modifier expression variables

When the after-hours or holiday modifier is an expression rather than a plain number, two variables are in scope:

Variable Type Description
rate number The base hourly rate for this tag
hours number How many hours of the current entry fall inside the after-hours or holiday window. If an entry runs from 20:00 to 23:00 and after-hours begins at 21:00, the modifier expression is evaluated once for the 21:00–23:00 piece, so hours is 2.0 — not the total entry duration.

Rate expression: entry references

A rate expression has no fixed built-in variables. Instead, it uses an entry reference system: strings embedded in the expression that Stundu substitutes with data from your recorded entries before the expression is evaluated.

Reference syntax

[tag][position][field]   — entries for a specific tag
[position][field]        — same-tag shorthand (tag inferred from current entry)

Fields

Field Type Description
hours number Full recorded duration of the referenced entry, from its start to its stop, in hours.
start_hour number Hour the entry started, as a decimal (09:30 → 9.5)
weekday number Day of the week: Monday = 0 … Sunday = 6

Positions

Position Returns Meaning
[-1] scalar Current entry
[-2] scalar Previous entry
[0] scalar First entry ever
[:-1] array All entries before the current
[0M:+1M] array This calendar month
[-1M:0M] array Last calendar month
[0W:+1W] array This calendar week
[-7d:] array Last 7 days

Scalar positions resolve to a single number. Slice positions (any position containing :) resolve to an array — pass them to aggregate functions such as Sum or Max.

How substitution works

Before the expression is evaluated, Stundu walks through it and replaces every entry reference string with a concrete number (or array). The expression then evaluates against those plain values.

Say you have a tag Client A and you record a 2-hour entry on a Wednesday. When Stundu calculates the rate:

  • "[-1][hours]"2.0 (duration of the current entry)
  • "[-1][weekday]"2 (Wednesday, counting from Monday = 0)
  • "[0M:+1M][hours]"["Array", 3.0, 1.5, 2.0] (hours of every Client A entry this month, including the current one)

So the retainer expression

["If", [["Less", ["Sum", "[0M:+1M][hours]"], 10], 0], 80]

becomes, after substitution:

["If", [["Less", ["Sum", ["Array", 3.0, 1.5, 2.0]], 10], 0], 80]

Sum collapses the array to 6.5, which is less than 10, so the rate evaluates to 0 — this entry is still within the free retainer window.

To reference a different tag, prefix the position with the tag name: "[Client B][0M:+1M][hours]" sums hours logged under Client B this month, regardless of which tag the current entry carries.

Enable expression mode

In the rate form, check the Expression checkbox. The amount field is replaced by a textarea where you can enter a MathJSON expression.

Preview

Click Preview to run the expression against your real entries for that tag. The result is shown inline — you can iterate on the formula before saving.

Examples

Goal Expression
Flat rate 50
Retainer: first 10 h free, then €80/h ["If", [["Less", ["Sum", "[0M:+1M][hours]"], 10], 0], 80]
Weekend surcharge: €120/h Sat–Sun, €80/h Mon–Fri ["If", [["GreaterEqual", "[-1][weekday]", 5], 120], 80]
After-hours modifier: 1.5× base rate ["Multiply", rate, 1.5]

Note

Expression-based rates are an advanced feature. If your billing is straightforward, a flat rate is all you need.

Running a billing report

Go to the Report tab to calculate billable amounts for a date range.

  1. Set a From and To date (or use the shortcuts: this week, last week, this month, last month).
  2. Optionally filter by tag.
  3. Click Show.

The report lists every entry with its duration and billed amount, and shows a total at the bottom. Click Copy as Markdown to copy the report to the clipboard in a format suitable for invoices.