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¶
- Go to the Tags tab.
- Click Rate on any tag to open the rate modal.
- Go to the Rates tab.
- Enter an amount, a currency, and an effective-from date.
- 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.
- Open the rate modal for a tag.
- Go to the Modifiers tab.
- Set an after-hours multiplier and an effective-from date.
- 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
becomes, after substitution:
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.
- Set a From and To date (or use the shortcuts: this week, last week, this month, last month).
- Optionally filter by tag.
- 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.