How the estimate is calculated
The rate engine inrates.py is pure functions with no I/O. Every figure shown in the product is produced by two formulae:
meeting_date, sums the per-minute costs, and applies the configured loading_factor (default 1.30) to account for employer costs on top of base salary.
Money crosses the wire as a string, not a float. A JSON number is an IEEE-754 double, which cannot represent amounts like
1234.56 exactly, and these figures end up in finance reports. Always parse them as decimals on the client side.Calling the endpoint
POST /api/v1/estimate is called on every edit to a draft invite list — one round trip, two database queries, no fan-out to external services.
Request fields
- curl
- Response
Cadence and the annualised total
Thecadence field drives annualised_total. For a one_off meeting annualised_total is always null — an annual figure for a meeting that happens once would be meaningless. For every other cadence, MeetCost multiplies the per-occurrence cost by the occurrences-per-year for that cadence:
The annualised figure is computed from the same per-occurrence total the organiser is already looking at, not from a separate re-estimation.
Nudge prompts
Nudges are inline, non-blocking prompts shown at scheduling time. Three threshold types are admin-configurable under Rates & policy; setting a threshold to zero disables that nudge type entirely.cost_above_threshold
Fires when
cost.total exceeds the configured nudge_cost_threshold. Suggests whether a written update could do the same job.headcount_above_threshold
Fires when
headcount exceeds nudge_headcount_threshold. Suggests that attendees who only need the outcome could read the notes instead.duration_above_threshold
Fires when
duration_minutes exceeds nudge_duration_minutes_threshold. Shows how much halving the duration would save.annualised nudge is also appended for any cadence other than one_off — this one is always present for recurring meetings and is not threshold-gated.
Nothing in v1 blocks the organiser from proceeding. Nudges are informational only.
The draft_id workflow
The draft_id is an optional, client-generated string that links successive estimates for the same draft invite. When provided, each response includes a snapshot_id so the sequence of changes is traceable.
1
First estimate
The add-in generates a
draft_id (e.g. the calendar client’s native event ID) and includes it on every estimate call as the organiser edits the invite list.2
Successive edits
Each subsequent
POST /api/v1/estimate for the same draft_id appends a new snapshot, recording headcount, duration, total cost, and cadence at that point in time.3
Commit on send
When the organiser actually sends the invite, the add-in calls The commit response shows whether the meeting was reduced before sending:
POST /api/v1/estimate/commit:Fallback rates and estimated_headcount
Not every invitee will have a pay record. When no record applies to a person on meeting_date, MeetCost prices them at the org-wide fallback_annual_pay configured under Rates & policy. The response field estimated_headcount counts how many attendees were priced this way.
When estimated_headcount is non-zero the response also includes a human-readable coverage_note you can surface directly to the organiser.
Attendees who are not in the directory at all (no directory entry and no pay record) are classified as external and also priced at the fallback rate, with rate_source set to EXTERNAL rather than FALLBACK in the per-person breakdown.
409 Conflict: multi-currency meetings
If the invitees hold pay records in more than one currency MeetCost returns409 Conflict rather than silently summing across currencies. The response body identifies which currencies collided. Currency conversion is not supported in v1.
Loading factor and working hours
TwoRateParams values govern the formula for salaried staff:
Both are configurable via
PATCH /api/v1/admin/config. The working_hours_per_year divisor is not applied to hourly staff: someone paid £45/hour is costed at £45/hour directly, because annualising an hourly rate and dividing it back down reintroduces exactly the assumption that makes the number wrong for them.