Skip to Content
Internal docs are powered by Nextra Docs Theme.
ProjectsSLA Calculator

SLA Calculator: Product and Implementation Plan

Summary

Build an SLA Calculator on the SLA Policies index page. It lets an administrator:

  1. Create a standalone SLA configuration from form controls.
  2. Calculate that configuration against a selected historical period.
  3. Inspect summary metrics, weekly results, and the exact tickets behind the result.
  4. Save the configuration so it can be reopened and calculated again.

The calculator is not based on an existing SLA policy and does not modify production SLA policies. A saved calculator configuration is a reusable analytical object, not an active policy.

The initial customer use case is EasyPay / Lesaka ADP’s request to model new SLA rules and recalculate the preceding eight weeks before deciding whether to operationalize those rules.

Why We Need This

Customers periodically ask us to answer questions such as:

  • What would our SLA percentage have been if this issue type had been excluded?
  • What changes if selected ticket statuses pause the SLA clock?
  • What happens when only business hours and public holidays count?
  • Can we see the result for the last eight weeks before changing the live policy?
  • Which exact tickets are counted as met or out of SLA, and why?

Today these requests require a mixture of live policy edits, report instructions, one-off queries, and manual interpretation. That is difficult to reproduce and creates a risk that a report’s prose describes rules that the SLA engine did not actually use.

The calculator should provide one deterministic implementation of the SLA calculation that can be used by the UI and, later, by Instruct. It should produce explainable results without activating or mutating a live SLA policy.

Customer Context: EasyPay / Lesaka ADP

Lesaka asked for an updated view of the preceding eight weeks with:

  • Weekly ticket cohorts running Wednesday through Tuesday.
  • Results evaluated through the end of the following Wednesday.
  • A 24-hour overall SLA with a paired exclusion.
  • A 24-business-hour agent SLA with pause statuses, business hours, and South African public holidays.
  • A four-hour P1 SLA for selected issue types.
  • SLA attainment and average resolution-time outputs.
  • Open tickets at the reporting cutoff counted as out of SLA.

Their relevant production configuration and data currently include:

  • Organization timezone: Africa/Johannesburg.
  • SLA reporting week starts on Wednesday.
  • Conversation fields named EP Issue Type, Collector Names, and ESP Priority.
  • Ticket statuses Waiting for customer, Waiting for 3rd Party, and Waiting for Support.
  • A South African public-holiday calendar.
  • Existing work schedules, including a discarded 08:00–17:00 weekday schedule and an active 24/7 P1 schedule.
  • An active 24-hour resolution policy that does not yet express all the requested filters and pause behavior.

The fields described by the customer as arbitrary ticket fields are conversation fields in Rulebase and should be presented that way in the calculator.

Product Model

SLA configuration

An SLA configuration is a saved, reusable definition containing:

  • A name and optional description.
  • Ticket cohort and reporting-window rules.
  • Timezone.
  • Conversation-field and standard ticket filters.
  • SLA target type and duration.
  • Calendar-time or business-time behavior.
  • Pause statuses.
  • Work schedule and holiday calendar.
  • Result grouping options.

It is deliberately independent of the organization’s current SLA policies. The administrator starts with an empty configuration, rather than selecting an existing policy to compare against.

Calculation run

A calculation run applies one immutable snapshot of a configuration to a historical date range. A run records:

  • Configuration version or snapshot.
  • Selected historical period.
  • Data-as-of timestamp.
  • Run status and any validation errors.
  • Aggregate results.
  • Weekly results.
  • Ticket-level results and explanations.

Saving a configuration and retaining calculation runs are different operations. A configuration can be edited and recalculated; an older run must still identify the exact configuration snapshot that produced it.

Relationship to live SLA policies

For the first version, a calculator configuration does not enforce anything and does not change live policy outcomes.

A later feature may allow Create policy from configuration, but that must be an explicit, separate action with its own validation. It should not be part of the initial save flow.

Primary User Flow

From Settings → SLA Policies:

  1. Click SLA Calculator.
  2. Click New configuration.
  3. Enter a name and define the SLA configuration using form controls.
  4. Select a historical period, such as the last eight completed reporting weeks.
  5. Click Calculate.
  6. Review headline metrics, weekly results, and ticket-level evidence.
  7. Adjust the configuration and calculate again if needed.
  8. Click Save configuration to retain it for later use.

Calculation should happen on an explicit button click, not after every form edit. If the user changes the form after calculating, the UI should clearly mark the displayed result as stale until it is recalculated.

Configuration Form

Identity

  • Configuration name.
  • Optional description.

Historical period

  • Preset ranges such as last 4, 8, or 12 completed reporting weeks.
  • Custom start and end dates.
  • Timezone.

The selected period is an input to a calculation run. It does not necessarily need to be part of the reusable configuration unless the user explicitly saves it as a default range.

Ticket cohort and reporting window

  • Reporting frequency: weekly initially.
  • Week start day.
  • Cohort start time.
  • Cohort end day/time, normally derived from the weekly boundary.
  • Reporting cutoff or observation window after the cohort closes.

For Lesaka, a cohort contains tickets created from Wednesday 00:00 through Tuesday 23:59:59 SAST. Events are observed through Thursday 00:00 SAST, which is the end of the following Wednesday.

Ticket filters

The filter builder must support:

  • Standard ticket attributes such as channel, brand, tags, assignee, and priority where available.
  • Conversation fields and their actual values.
  • Operators such as equals, does not equal, is one of, is not one of, is empty, and is not empty.
  • Nested AND / OR groups.

Nested logic is essential. Lesaka’s exclusion is a paired condition:

Exclude when: EP Issue Type = Airtime Stock Order AND Collector Names = Shoprite

It must not be interpreted as excluding every Airtime Stock Order ticket or every Shoprite ticket independently.

SLA clock

  • Target event, initially resolution.
  • Target duration and unit.
  • Clock type: calendar time or business time.
  • Pause statuses.
  • Work schedule when business time is selected.
  • Holiday calendar when applicable.

Result grouping

  • Overall.
  • By agent.
  • Both overall and by agent.

Grouping changes the presentation and aggregation, not the underlying ticket outcome.

Open tickets at the cutoff

There should be no “Open-at-cutoff treatment” form control.

The calculation follows the reporting semantics consistently. For this Lesaka report, a ticket that is unresolved at the reporting cutoff is reported as out of SLA. The ticket table separately exposes Status at cutoff so the user can see that the ticket was open and which historical status it occupied.

The status must be reconstructed from ticket history at the cutoff. It must not use the ticket’s current status, since a ticket that was open during a historical reporting window may have since been resolved.

Lesaka Configurations

The customer’s requested outputs can be produced by three saved configurations. Average resolution time is an output of each calculation, not a separate configuration.

1. 24h SLA — Overall

Identity

  • Name: Lesaka 24h SLA — Overall
  • Timezone: Africa/Johannesburg

Reporting window

  • Frequency: weekly.
  • Cohort: Wednesday 00:00 through Tuesday 23:59:59.
  • Observation cutoff: Thursday 00:00 after the following Wednesday.
  • Historical range for the requested run: last eight completed reporting weeks.

Eligibility filter

Include all cohort tickets EXCEPT when: EP Issue Type = Airtime Stock Order AND Collector Names = Shoprite

SLA clock

  • Target event: resolution.
  • Target: 24 hours.
  • Clock: calendar time.
  • Pause statuses: none.
  • Work schedule: none.
  • Holiday calendar: none.

Outputs

  • Overall SLA percentage.
  • Met and out-of-SLA counts.
  • Average resolution time.
  • Weekly breakdown.
  • Ticket-level results.

2. 24h SLA — Agent

Identity

  • Name: Lesaka 24h SLA — Agent
  • Timezone: Africa/Johannesburg

Reporting window

  • Frequency: weekly.
  • Cohort: Wednesday 00:00 through Tuesday 23:59:59.
  • Observation cutoff: Thursday 00:00 after the following Wednesday.
  • Historical range for the requested run: last eight completed reporting weeks.

Eligibility filter

Include all cohort tickets EXCEPT when: EP Issue Type = Airtime Stock Order AND Collector Names = Shoprite

SLA clock

  • Target event: resolution.
  • Target: 24 business hours.
  • Pause while status is any of:
    • Waiting for customer
    • Waiting for 3rd Party
    • Waiting for Support
  • Business hours: Monday–Friday, 08:00–17:00.
  • Timezone: Africa/Johannesburg.
  • Weekend work: excluded.
  • Holiday calendar: South Africa Public Holidays.

Outputs

  • Overall SLA percentage.
  • SLA percentage by agent.
  • Met and out-of-SLA counts.
  • Average counted resolution time overall and by agent.
  • Weekly breakdown.
  • Ticket-level results.

3. P1 4h SLA — Overall & Agent

Identity

  • Name: Lesaka P1 4h SLA — Overall & Agent
  • Timezone: Africa/Johannesburg

Reporting window

  • Frequency: weekly.
  • Cohort: Wednesday 00:00 through Tuesday 23:59:59.
  • Observation cutoff: Thursday 00:00 after the following Wednesday.
  • Historical range for the requested run: last eight completed reporting weeks.

Eligibility filter

EP Issue Type is one of: - Token not Working - Unable to Vend to Meter - No Token Provided

The implementation should use the exact stored conversation-field values while showing human-readable labels in the UI.

SLA clock

  • Target event: resolution.
  • Target: 4 hours.
  • Clock: calendar time, based on the customer’s current wording.
  • Pause statuses: none.
  • Work schedule: none.
  • Holiday calendar: none.

Outputs

  • Overall SLA percentage.
  • SLA percentage by agent.
  • Met and out-of-SLA counts.
  • Average resolution time overall and by agent.
  • Weekly breakdown.
  • Ticket-level results.

Open question to preserve in the UI/design: the customer did not specify separate clock rules for P1 Agent. The default interpretation is one P1 calculation displayed overall and grouped by agent. If P1 Agent should inherit the 24h Agent pauses and business schedule, it becomes a fourth configuration or a materially different P1 configuration.

Expected Calculation Output

Headline summary

  • Eligible tickets.
  • Excluded tickets.
  • Met tickets.
  • Out-of-SLA tickets.
  • SLA attainment percentage.
  • Average resolution time.
  • Date range.
  • Reporting timezone.
  • Data-as-of timestamp.

The UI should display the numerator and denominator alongside the percentage. A percentage without its counts is hard to audit.

Weekly breakdown

One row per reporting week:

Reporting weekEligibleMetOut of SLASLA %Average resolution
2026-09-09 to 2026-09-1510091991.0%12h 18m

Dates above are illustrative. The UI should label each week using the organization’s timezone and make the cohort boundary clear.

Ticket-level result table

Each eligible ticket should show:

ColumnPurpose
TicketLink or identifier for inspection
Created atCohort membership and clock start
Status at cutoffHistorical status reconstructed at the reporting cutoff
Resolved atResolution event used by the calculation
SLA deadlineCalculated deadline under this configuration
Calendar elapsedTotal wall-clock duration
Paused timeTime removed because of pause statuses
Non-business timeTime removed by schedule/weekends/holidays
Counted SLA timeTime that actually counted against the target
SLA resultMet or Out of SLA
Matched filtersWhy the ticket was included
AgentAgent used for grouping, with its attribution point defined

The table should support:

  • Filtering by result, week, status at cutoff, and agent.
  • Sorting by deadline, counted SLA time, or resolution time.
  • Searching for a ticket identifier.
  • CSV export.

Excluded tickets should be available in a separate view or filter with a clear exclusion reason. They should not silently disappear.

Per-ticket explanation

Selecting a ticket should explain the calculation in plain language, for example:

Created Tue 16:42 SAST Target: 24 business hours 9h 00m counted before the reporting cutoff 7h 18m excluded outside business hours 2h 11m paused while Waiting for Customer Status at cutoff: Waiting for Support Result: Out of SLA because it was unresolved at the reporting cutoff

This explanation is important for support, customer conversations, and validating edge cases.

Calculation Semantics

The calculator should use one deterministic domain service rather than duplicating logic in a report query or UI.

Time boundaries

  • Convert user-entered local boundaries using the configured IANA timezone.
  • Treat the cohort as a half-open interval: created_at >= cohort_start and created_at < cohort_end.
  • Treat the observation period as ending at the exact reporting cutoff.
  • Handle daylight-saving changes through timezone-aware date libraries, even though South Africa does not currently observe daylight saving time.

Historical state

Calculations must use event history as it existed during the selected period:

  • Status at cutoff comes from the last status event at or before the cutoff.
  • Resolution time comes from the qualifying resolution event at or before the cutoff.
  • Pause intervals are derived from historical status transitions and clipped to the calculation interval.
  • Conversation-field filters should use the correct historical value where history exists. If only the current value is available, the run must disclose that limitation rather than implying historical precision.
  • Agent grouping must define whether the agent is taken at resolution, deadline, or cutoff. For this use case, agent at resolution when resolved and agent at cutoff when unresolved is a reasonable default, but the rule must be explicit and stable.

Outcome classification

For the Lesaka calculation:

  • Resolved within the counted target: Met.
  • Resolved after the counted target: Out of SLA.
  • Unresolved at the reporting cutoff: Out of SLA.

There is no user-facing open-ticket treatment toggle.

The underlying reusable engine should still distinguish the reason for the result, such as resolved_after_deadline, deadline_passed_unresolved, or unresolved_at_reporting_cutoff, so the UI can explain it and future reporting semantics do not require parsing display strings.

Average resolution time

  • Calculate the average using eligible tickets resolved by the reporting cutoff.
  • Overall configurations use calendar elapsed time unless configured otherwise.
  • Agent configurations use counted SLA time after subtracting pauses and non-business periods.
  • Do not silently treat unresolved tickets as having a resolution duration.
  • Show the number of resolved tickets contributing to the average.

This means the SLA percentage denominator and average-resolution denominator can differ. The UI must label both counts.

Filters and missing values

  • Use explicit boolean expression trees for nested filter logic.
  • SQL NULL behavior must not accidentally exclude tickets that do not match a paired exclusion.
  • A ticket with a missing Collector Names value does not satisfy Collector Names = Shoprite and therefore does not match the paired exclusion.
  • Store stable field IDs and option values in the configuration, with display labels copied for readability and audit history.

Suggested Data Shape

The exact schema can follow existing Rulebase conventions, but the domain payload should resemble:

{ "name": "Lesaka 24h SLA — Agent", "description": "Historical calculator configuration for the Lesaka weekly SLA report", "timezone": "Africa/Johannesburg", "cohort": { "frequency": "weekly", "weekStartsOn": "wednesday", "startTime": "00:00" }, "reportingCutoff": { "daysAfterCohortEnd": 1, "time": "00:00" }, "eligibility": { "operator": "not", "condition": { "operator": "and", "conditions": [ { "fieldType": "conversation_field", "fieldId": "conversation_field_8AJ...", "operator": "equals", "value": "Airtime Stock Order" }, { "fieldType": "conversation_field", "fieldId": "conversation_field_Xl0...", "operator": "equals", "value": "Shoprite" } ] } }, "target": { "event": "resolution", "duration": 24, "unit": "hours", "clock": "business" }, "pauseStatuses": [ "Waiting for customer", "Waiting for 3rd Party", "Waiting for Support" ], "schedule": { "timezone": "Africa/Johannesburg", "weekdays": { "monday": [{ "start": "08:00", "end": "17:00" }], "tuesday": [{ "start": "08:00", "end": "17:00" }], "wednesday": [{ "start": "08:00", "end": "17:00" }], "thursday": [{ "start": "08:00", "end": "17:00" }], "friday": [{ "start": "08:00", "end": "17:00" }] }, "holidayCalendarId": "south-africa-public-holidays" }, "groupings": ["overall", "agent"] }

The reporting cutoff representation should be refined during implementation so it cannot produce ambiguous boundaries. The important requirement is that it describes the observation window independently of the target duration.

Technical Direction

One shared calculation engine

Extract or extend a side-effect-free SLA calculation service that accepts:

  • A configuration snapshot.
  • A historical range.
  • Ticket and event-history inputs.
  • A data-as-of timestamp.

It returns typed aggregate, weekly, and ticket-level results with reason codes. The same engine should eventually power:

  • The SLA Calculator UI.
  • Scheduled/frozen SLA outcomes where the semantics match.
  • An Instruct tool for calculating a structured SLA configuration.

Instruct should translate natural-language requests into the same validated configuration schema and invoke the calculator. It should not independently generate SQL or infer results from prose.

Do not calculate from frozen outcomes alone

Existing frozen weekly outcomes represent the policy and engine behavior at the time they were created. They cannot answer a hypothetical configuration with different filters, schedules, pause statuses, or targets.

The calculator must recalculate from source tickets and their historical events. Existing outcomes may be used for parity testing when the calculator configuration exactly matches a live policy, but they are not the source of truth for hypothetical runs.

Asynchronous execution

An eight-week calculation can involve many tickets and historical events. Treat calculation as an asynchronous run:

  1. Validate and persist the immutable run input.
  2. Enqueue calculation after commit.
  3. Process in bounded weekly or ticket batches.
  4. Persist progress and results.
  5. Let the UI poll or subscribe for completion.

Do not hold database locks or transactions while processing the run.

Reproducibility

Every completed run should retain:

  • Configuration snapshot and schema version.
  • Configuration ID and version, if saved.
  • Requested historical range.
  • Data-as-of timestamp.
  • Engine version.
  • Result counts and ticket result records.

This lets support reproduce a customer-visible number even after the saved configuration is edited.

Implementation Phases

Phase 1: Calculation domain and parity fixtures

  • Define the versioned configuration schema and validation.
  • Define filter expression trees with conversation-field support.
  • Define calculation result and reason-code types.
  • Implement historical cohort selection and event reconstruction.
  • Implement calendar clocks, business clocks, pauses, holidays, and cutoffs.
  • Add Lesaka-shaped fixtures covering paired exclusions, open tickets, pause intervals, business hours, weekends, and holidays.
  • Add parity tests against existing policy outcomes for configurations that should behave identically.

Exit condition: the engine deterministically produces auditable ticket outcomes for all three Lesaka configurations.

Phase 2: Saved configurations and calculation runs

  • Add persistence for calculator configurations and immutable versions/snapshots.
  • Add persistence for runs, aggregate results, and ticket results.
  • Add create, read, update, list, calculate, run-status, and run-result API endpoints.
  • Add authorization using the existing SLA-policy administration permission model.
  • Add asynchronous execution, progress, retries, and failure reporting.

Exit condition: an API client can save a standalone configuration, calculate eight weeks, and retrieve reproducible results.

Phase 3: SLA Calculator UI

  • Add the SLA Calculator entry point to the SLA Policies index.
  • Build the new-configuration form.
  • Load standard fields, conversation fields, statuses, schedules, and holiday calendars into selectors.
  • Build nested filter groups.
  • Add explicit Calculate and Save configuration actions.
  • Display stale-result state after form edits.
  • Build summary, weekly, and ticket-level result views.
  • Add per-ticket explanations and CSV export.
  • Update Instruct’s app-navigation system skill because this adds a settings screen/route.

Exit condition: a Rulebase administrator can create, calculate, inspect, and save each Lesaka configuration without engineering assistance.

Phase 4: Instruct integration

  • Add a structured calculate_sla_configuration or equivalent tool backed by the same schema and engine.
  • Allow Instruct to draft a configuration from customer language.
  • Require the user to review the structured inputs before a potentially expensive calculation.
  • Link Instruct results to the saved configuration and calculation-run UI.

Exit condition: Instruct can help populate and run the calculator without implementing a second SLA calculation path.

Possible future phase: Create a live policy

  • Validate which calculator concepts map directly to enforceable SLA policy concepts.
  • Show unsupported reporting-only settings.
  • Create a draft policy from a saved configuration through an explicit action.
  • Keep policy activation separate from creation.

This is intentionally outside the initial calculator scope.

Acceptance Criteria

Configuration behavior

  • A user can start from an empty configuration without selecting a live SLA policy.
  • A user can select standard fields and conversation fields in filters.
  • Nested AND / OR / NOT logic preserves Lesaka’s paired exclusion.
  • A user can choose calendar or business time, pause statuses, a schedule, and a holiday calendar.
  • A user can calculate before saving and save the configuration afterward.
  • Saving does not activate or alter an SLA policy.
  • Editing a form after calculation marks the displayed result as stale.

Results

  • Results include ticket counts and denominators alongside percentages and averages.
  • Results include an eight-week breakdown for the requested Lesaka run.
  • Every eligible ticket has a reasoned outcome.
  • Excluded tickets are inspectable with exclusion reasons.
  • Status at cutoff reflects historical status, not current status.
  • Unresolved Lesaka tickets at cutoff are shown as out of SLA.
  • Average resolution results disclose the number of resolved tickets included.
  • Results can be exported.

Correctness and auditability

  • Re-running an unchanged configuration against the same data-as-of snapshot produces the same result.
  • A completed run retains its original configuration after the saved configuration is edited.
  • Null conversation-field values do not break paired exclusion logic.
  • Pause intervals and non-business intervals are not double-subtracted.
  • Events outside the observation cutoff cannot affect a historical result.
  • Timezone boundaries, weekends, and South African public holidays are covered by tests.

Important Edge Cases

  • Ticket created exactly at the cohort start or next cohort boundary.
  • Ticket resolved exactly at its SLA deadline.
  • Ticket resolved exactly at the reporting cutoff.
  • Ticket reopened after resolution.
  • Ticket changes between multiple pause statuses without resuming.
  • Ticket remains in a pause status at cutoff.
  • Pause interval overlaps non-business time.
  • Ticket has no value for one side of a paired exclusion.
  • Conversation field is renamed or an option is removed after a run.
  • Assignee changes before deadline, at resolution, or before cutoff.
  • Holiday falls on a weekend.
  • Work schedule changes after the historical period.
  • Ticket is currently resolved but was open at the historical cutoff.
  • Duplicate or late-arriving source events change historical data after an earlier run.

Out of Scope for the Initial Version

  • Automatically activating a live SLA policy.
  • Mutating or backfilling frozen production SLA outcomes.
  • Comparing against a current policy as a required starting point.
  • A user-configurable open-at-cutoff treatment.
  • Arbitrary natural-language calculations that bypass the structured configuration.
  • Editing source ticket data from the calculator.

Product Decisions Captured

  • The feature is called SLA Calculator.
  • It lives from the SLA Policies index.
  • New configurations start independently of current policies.
  • The core flow is create configuration → calculate → inspect → save configuration.
  • Calculation is explicit, not live on every field change.
  • Saved configurations are reusable analytical objects, not policy drafts.
  • Conversation fields are first-class filter inputs.
  • Average resolution metrics are outputs, not separate SLA configurations.
  • There is no open-at-cutoff treatment selector.
  • The report includes a historical Status at cutoff column.
  • The first Lesaka interpretation uses three configurations; P1 overall and agent are two groupings of the same calculation unless the customer supplies distinct P1 Agent clock rules.

Remaining Product Questions

These do not block the overall direction, but should be resolved before implementation is considered complete:

  1. Should historical conversation-field filters use current values when field-value history is unavailable, or should those tickets be marked indeterminate?
  2. What is the canonical agent attribution point for unresolved tickets and for tickets reassigned during the SLA interval?
  3. Should saved configurations include a default historical range, or should date selection belong only to each run?
  4. How long should ticket-level run results be retained?
  5. Should users be allowed to duplicate a saved configuration?
  6. Does Lesaka intend P1 Agent to use calendar hours, or inherit the 24h Agent business schedule and pause statuses?

The UI and schema should avoid baking in an answer to these questions accidentally. Where possible, make the chosen behavior explicit in the configuration snapshot or run metadata.

Last updated on