Calculation Engine – System Documentation
This document explains how the Award Interpreter calculation engine derives rates, segments shifts, and calculates shift and roster costs. Reference data (awards, classifications, penalties and allowances) is loaded from the database — see the Reference Data tab to browse those tables.
1. Data Relationships
Note: Raw penaltyCalculatedValue figures from the database may differ from the above. The multiplier validation layer (Section 2.3.1) detects and overrides out-of-range values. For MA000004 CA Level 1, tiered Saturday rows are discarded — see Section 2.4.
1.1 Classification Selection
When a user selects:
- Award: Filters to that award's data
- Employment Type: Filters classifications by
employeeRateTypeCode(CA = Casual, FT = Full-time, PT = Part-time) - Classification: Selects the specific classification row
1.2 Penalty Matching
STEP 4 Fix: A penalty row applies if:
penalty.awardCode === selectedAwardCode- Classification Matching (Tightened Logic):
- For non-AD penalties: BOTH
penalty.classificationANDpenalty.classificationLevelmust match the selected classification - For AD (applies to all) penalties:
penalty.classificationmatches ORpenalty.classificationLevelmatches
This prevents cross-contamination where Level 2 penalties incorrectly match Level 1 queries.
- For non-AD penalties: BOTH
penalty.employeeRateTypeCodematches selected rate type OR is "AD" (applies to all)
Selection: Award MA000004, Casual (CA), Retail Employee Level 1
Matching Penalties:
- ✓ "Saturday - ordinary hours" (CA, Retail Employee Level 1)
- ✓ "Sunday - ordinary hours" (AD, Retail Employee Level 1)
- ✗ "Overtime - Monday to Saturday" (FT, Retail Employee Level 1) - wrong rate type
1.3 Engine inputs
The shift/roster cost engine uses the following inputs. All of these affect the final cost.
| Source | Input | Description |
|---|---|---|
| User (UI) | Award | Selected award code (e.g. MA000004) |
| Employment type | Selected rate type (e.g. CA = Casual, FT, PT). Drives classification lookup and whether casual loading applies. | |
| Classification | Selected classification name (e.g. Retail Employee Level 1) | |
| Casual loading % | Number 0–100. Default 25. Persisted in localStorage. When user selects Casual and classification has weekly base, this is used to derive the ordinary hourly rate. 0% is valid (no loading). | |
| Per shift: Date | Shift start date (YYYY-MM-DD) | |
| Per shift: Start time | Shift start time (HH:mm, 24h) | |
| Per shift: Duration (hrs) | Total shift length in hours | |
| Per shift: Break (min) | Unpaid break minutes; deducted from paid hours | |
| Per shift: Kms | Kilometres (for per-km expense allowances) | |
| Wage / expense allowances | Selected allowance types from the award | |
| Data (database + selection) | Classification row | baseRate, baseRateType, calculatedRate, calculatedRateType, employeeRateTypeCode, etc. |
| Penalty rows | All penalties matching award + classification + level + rate type (see 1.2) | |
| Public holidays | Optional list of YYYY-MM-DD dates (e.g. from state/territory) |
1.4 Calculation flow overview
High-level order of operations for a single shift:
- Resolve ordinary hourly rate — From classification and (when applicable) Casual loading % (see Section 2.1).
- Build penalty rate map — Map penalty keys (ordinary, saturday, sunday, etc.) to dollar rates; apply multiplier validation and MA000004 Saturday fix; when using casual loading for rate, set Sunday to ordinary × 1.50.
- Segment the shift — Split shift into 6-minute segments by day type (public holiday, Sunday, Saturday, weekday) and time-of-day (before 7am, 7am–6pm, after 6pm; Friday late). Apply overtime after 9 ordinary hours.
- Apply casual minimum engagement — If casual and paid hours < 3, pad to 3 hours at the shift’s day-type rate.
- Sum segment costs — For each segment: hours × rate from penalty map.
- Add wage and expense allowances — Using same effective hourly rate for percentage-based allowances.
- Return segments, allowances, total cost, total hours, and warnings.
2. Rate Calculation Logic
2.0 Casual loading and “use loading for rate”
The UI exposes a Casual loading % field (0–100, default 25, persisted). The engine uses it only when both of the following are true:
- The user has selected Employment type: Casual (CA) (from the dropdown).
- The selected classification has a weekly base rate (
baseRateType= "Weekly" andbaseRatepresent).
When both are true, the engine treats this as “use casual loading for rate”: the ordinary hourly rate is derived from the weekly base plus the user’s loading %, and the same rate is used consistently for the shift (and for percentage-based allowances). When the user has set 0%, the multiplier is 1.00 (no loading). Default 25% is used only when the value is missing or not a number.
loadingMultiplier = 1 + (casualLoadingPercent / 100) // 0% → 1.00, 25% → 1.25
ordinaryHourly = (baseRate ÷ 38) × loadingMultiplier // when "use loading for rate"
The “Calculated rate” shown in the Base and penalty rates panel uses this same formula when Casual is selected and the classification has a weekly base, so the displayed rate matches the rate used in the roster cost breakdown.
2.1 Ordinary hourly rate derivation
The system derives the ordinary hourly rate used for the shift in this order:
ordinaryHourly = (baseRate ÷ 38) × (1 + casualLoadingPercent/100)This overrides the database calculated rate so the user’s Casual loading % is always applied when applicable. 0% gives base/38.
calculatedRateType is "Hourly" and calculatedRate exists, use calculatedRate.
baseRateType is "Weekly" and baseRate exists:For Casual (CA):
ordinaryHourly = (baseRate ÷ 38) × loadingMultiplierFor Full-time/Part-time:
ordinaryHourly = baseRate ÷ 38
calculatedRate if available.
baseRate: $1008.90/week- Casual loading %: 25
- Result: ordinaryHourly = (1008.90 ÷ 38) × 1.25 = $33.19/hour (used for shift and for percentage allowances)
baseRate: $1008.90/week- Casual loading %: 0
- Result: ordinaryHourly = (1008.90 ÷ 38) × 1.00 = $26.55/hour
2.2 Penalty Rate Mapping
Penalty descriptions are normalized to internal keys:
| Penalty Description | Internal Key | Notes |
|---|---|---|
| "Saturday - ordinary hours" | saturday_ordinary |
Saturday-specific ordinary rate |
| "Saturday - first 3 hours" | saturday_first_3 |
First X hours of work on Saturday |
| "Saturday - after 3 hours" | saturday_after_3 |
Hours after first X hours on Saturday |
| "Sunday - ordinary hours" | sunday |
All Sunday hours |
| "Public holiday" | publicholiday |
Public holiday hours |
| "Monday - ordinary hours" | ordinary |
Weekday ordinary hours (Mon-Fri) |
2.3 Penalty Rate Value
For each penalty, the system prefers:
penaltyCalculatedValue(pre-calculated hourly rate in dollars)- If percentage:
ordinaryHourlyRate × (rate ÷ 100) - If dollar amount: use
ratedirectly
2.3.1 Multiplier Validation Layer (Data Quality Protection)
STEP 2 Implementation: For MA000004 Casual Level 1 employees, the system includes a multiplier validation layer that protects against incorrect database data:
- Expected Multiplier Ranges: Each penalty key has an expected multiplier range:
Penalty Key Expected Multiplier Range ordinary×1.00 0.98 - 1.02 weekday_early_late×1.10 1.08 - 1.12 friday_late×1.15 1.13 - 1.17 saturday/saturday_ordinary×1.25 1.23 - 1.27 sunday×1.50 1.48 - 1.52 publicholiday×2.25 2.23 - 2.27 - Validation Process:
- Calculate implied multiplier:
impliedMultiplier = penaltyCalculatedValue ÷ ordinaryHourlyRate - Compare against expected range for that penalty key
- If outside range (±0.02 tolerance): Log DATA QUALITY WARNING and override with calculated rate
- Override rate:
ordinaryHourlyRate × expectedMultiplier
- Calculate implied multiplier:
- Warning Messages: When an override is applied, a warning is added to the API response:
"Rate override applied for [key]: database value $X.XX implies ×Y.YY which is outside expected range (min-max). Using calculated rate $Z.ZZ instead. Source row: [description] ([rateType], Level [level])" - Rationale: This validation layer ensures that incorrect
penaltyCalculatedValuefigures from the database (or penalties matched from wrong classifications) do not produce incorrect shift costs. The system automatically corrects data quality issues while alerting users to the problem.
2.4 Saturday Penalty Verification (BUG 4 Fix)
The system checks whether tiered Saturday rates exist in the database and whether they are legitimate for the selected award/classification/rate type:
- MA000004 CA Level 1 (known exception): Tiered Saturday rows (
saturday_first_X,saturday_after_X) are always discarded for this classification regardless of what the database contains. This is a confirmed data quality issue — the tiered rows in the database do not represent the correct award entitlement for this classification. The flat Saturday rate (×1.25 on ordinary hourly rate) is always used for all Saturday hours. - Other awards/classifications: If tiered rates exist and their implied multipliers pass the validation check in Section 2.3.1, tiered logic is applied. If only a flat rate exists (
saturday_ordinaryor genericsaturday), the flat rate is used for all Saturday hours. - Consistency check: If the Saturday ordinary rate exceeds the Sunday rate after validation, a warning is generated.
2.5 Sunday rate and validation (BUG 5 Fix)
When the engine is using casual loading for the ordinary rate (user selected Casual and classification has weekly base), the database Sunday rate is often based on the unloaded rate (e.g. 26.55 × 1.50 = 39.83). The engine therefore overrides the Sunday penalty rate to:
sundayRate = ordinaryHourly × 1.50
so that Sunday costs and validation stay consistent with the loaded ordinary rate. When not using casual loading for rate, the Sunday rate from the penalty map is used as-is.
The system also validates that the Sunday rate equals ordinary × 1.50. If the value in the map (after any override) differs beyond tolerance, a validation warning is generated:
Expected Sunday rate = ordinaryHourlyRate × 1.50
3. Shift Cost Calculation
For each shift, the engine computes paid hours (duration minus unpaid break), splits that time into segments by day type and time-of-day, looks up the rate for each segment from the penalty map, then sums costs and adds allowances:
segmentCost = segmentHours × ratePerHour (rate from penalty map for that segment's key)
shiftCost = Σ segmentCosts + Σ wageAllowanceCosts + Σ expenseAllowanceCosts
Standard full-time week is 38 hours (Fair Work); weekly base rates are divided by 38 to get an hourly equivalent.
3.0 Weekday Time-of-Day Penalties (BUG 1 Fix)
The system now splits weekday shifts by time boundaries and applies appropriate penalty rates:
| Time Window | Day | Penalty Key | Multiplier |
|---|---|---|---|
| Before 7:00am | Mon-Fri | weekday_early_late |
×1.10 |
| 7:00am - 6:00pm | Mon-Fri | ordinary |
×1.00 |
| After 6:00pm | Mon-Thu | weekday_early_late |
×1.10 |
| After 6:00pm | Friday | friday_late |
×1.15 |
Shift: Thursday, 5:00pm-9:00pm (4 hours)
- 5:00pm-6:00pm: Ordinary hours (1 hr × $26.55 = $26.55)
- 6:00pm-9:00pm: Weekday early/late (3 hrs × $29.21 = $87.63)
- Total: $114.18
3.1 Weekday Overtime Logic (BUG 2 Fix)
After 9 ordinary hours per day, overtime multipliers apply:
- First 3 hours of overtime: ×1.50 multiplier
- Beyond 3 hours of overtime: ×2.00 multiplier
Important: Overtime multipliers are applied on top of whichever time-of-day penalty is active at that moment.
Shift: Monday, 10:00am-10:00pm (12 hours)
- 10:00am-6:00pm: Ordinary hours (8 hrs × $26.55 = $212.40)
- 6:00pm-7:00pm: Weekday early/late (1 hr × $29.21 = $29.21)
- 7:00pm-10:00pm: Weekday early/late + overtime first 3 hrs (3 hrs × $43.81 = $131.43)
- Total: $373.04
Note: Rate calculation = $26.55 × 1.10 (late) × 1.50 (overtime) = $43.81/hr
3.2 Casual Minimum Engagement (BUG 3 Fix)
MA000004 Clause 13.3: Casual employees must be paid a minimum of 3 hours per engagement, regardless of actual hours worked.
- If paid hours < 3, the system pads to 3 hours
- FIX 1: Padding hours are charged at the shift's day-type rate, not always the ordinary rate:
- Weekday shift → padding at ordinary weekday rate
- Saturday shift → padding at applicable Saturday rate (saturday_ordinary or saturday)
- Sunday shift → padding at Sunday rate (×1.50)
- Public holiday shift → padding at public holiday rate
- A warning is added to the response: "Minimum casual engagement of 3 hours applied"
- Applies to all day types (weekdays, weekends, public holidays)
Shift: Sunday, 2 hours worked
- Actual hours: 2 hours
- Minimum engagement: 3 hours
- Padding: 1 hour at Sunday rate (×1.50 on casual loaded rate)
- Total paid hours: 3 hours
Note: The padding hour is charged at the Sunday rate, not the ordinary weekday rate, because the shift occurs on a Sunday.
Shift: Saturday, 2 hours worked
- Actual hours: 2 hours
- Minimum engagement: 3 hours
- Padding: 1 hour at the flat Saturday rate (×1.25 on ordinary hourly rate = $33.19/hr)
- Total paid hours: 3 hours
3.3 Shift Segmentation
The system processes a shift in 6-minute increments, determining the penalty key for each moment:
1. Check if it's a public holiday →
publicholiday
2. Check if it's Sunday →
sunday
3. Check if it's Saturday → apply Saturday logic
4. Otherwise → apply weekday time-of-day logic (see Section 3.0):
• Before 7am =
weekday_early_late (×1.10)
• 7am–6pm =
ordinary (×1.00)
• After 6pm Mon–Thu =
weekday_early_late (×1.10)
• After 6pm Fri =
friday_late (×1.15)
1. Track hours worked in shift (excluding breaks)
2. If hours worked < X and "saturday_first_X" exists → use that
3. If "saturday_after_X" exists → use that
4. If "saturday_ordinary" exists → use that
5. Fallback to generic "saturday" or "ordinary"
Shift: Saturday, 09:00-14:00 (5 hours), 30 min break
Paid hours: 4.5 hours
Calculation:
- All hours: 4.5 hrs at flat Saturday rate (×1.25)
- 4.5 hrs × $33.19 = $149.36
Note: For MA000004 CA Level 1, tiered Saturday rates are not used; see Section 2.4.
3.4 Segment Accumulation
Segments with the same penalty key are accumulated:
Saturday - all hours: 4.5 hrs × $33.19 = $149.36
Total shift cost: $149.36
3.5 Break Deduction
Unpaid breaks are excluded from paid hours:
- Break time is skipped during segmentation
- Only worked time is charged
Shift: 8 hours duration, 30 min break
Paid hours: 7.5 hours
The 30-minute break period is not included in any segment's cost calculation.
4. Allowance Calculations
Allowances use the same effective ordinary hourly rate as the shift when they are percentage-based: that rate is passed from the shift cost engine (and includes casual loading when “use loading for rate” applies). So wage allowances stay consistent with the rate shown in the roster breakdown.
4.1 Wage Allowances
Wage allowances are calculated based on paymentFrequency and rateUnit:
| Payment Frequency | Calculation | Example |
|---|---|---|
| Hourly / Per hour | allowanceAmount × paidHours (or rate × paidHours) |
$2.50/hour × 7.5 hrs = $18.75 |
| Per shift / Per day | allowanceAmount (flat rate) |
$15.00 per shift |
| Weekly | (allowanceAmount ÷ 38) × paidHours |
($50/week ÷ 38) × 7.5 hrs = $9.87 |
| Percentage (hourly) | (effectiveHourlyRate × rate%) × paidHours |
Same hourly rate as shift (e.g. $33.19 if 25% loading) |
| Percentage (per shift) | (effectiveHourlyRate × rate%) × 8 |
Same hourly rate as shift; 8 = standard shift hours |
4.2 Expense Allowances
Expense allowances are calculated based on paymentFrequency:
| Payment Frequency | Calculation | Example |
|---|---|---|
| Per km / Per kilometre | allowanceAmount × shiftKms |
$0.85/km × 50 km = $42.50 |
| Per shift / Per day / Per occasion | allowanceAmount (flat rate) |
$12.00 per shift |
| For each meal | allowanceAmount (assumes 1 meal per shift) |
$8.50 per meal |
5. Roster Cost Calculation
For multiple shifts:
- Each shift is calculated independently using the logic above
- Shift costs are summed
- Total hours are summed
- Allowances are calculated per shift (not aggregated)
Shift 1: Wednesday, 5 hrs → $132.75 (ordinary hours, 9am–2pm)
Shift 2: Saturday, 5 hrs → $165.94 (flat Saturday rate, 5 hrs × $33.19)
Total: 10 hrs, $298.69
6. Key Design Decisions
6.1 Saturday "First X Hours" Logic
Rationale: Awards typically specify "first X hours of work" on Saturday, meaning the first X hours of the employee's shift, not the first X hours of the calendar day.
6.2 Penalty Rate Precedence
Rationale: Some penalty rows (e.g., "Monday - ordinary hours") may specify rates that already include casual loading or other adjustments, so they should take precedence over the base classification rate.
6.3 Saturday Ordinary Hours
saturday_ordinary, not generic ordinary.
Rationale: Saturday has its own ordinary rate that differs from weekday ordinary rates, so it needs a separate key to be applied correctly.
7. Summary
The Award Interpreter system:
- Loads reference data from the database (awards, classifications, penalties, allowances)
- Accepts user inputs: award, employment type, classification, Casual loading % (0–100), and per-shift date, start time, duration, break, kms, and selected allowances (see Section 1.3)
- Matches penalties and allowances to the selected award/classification/level/rate type (Section 1.2)
- Derives the ordinary hourly rate: when user selected Casual and classification has weekly base, uses (base÷38)×(1 + casualLoadingPercent/100); otherwise uses database calculated rate or weekly fallback (Section 2.1)
- Builds the penalty rate map from matched penalties, with multiplier validation, MA000004 Saturday flat-rate fix, and when using casual loading for rate, Sunday = ordinary × 1.50 (Sections 2.2–4.5)
- Maps penalty descriptions to internal keys (ordinary, saturday_ordinary, sunday, etc.) for lookup
- Segments shifts by day type, time-of-day boundaries, and hours worked (6-minute precision)
- Applies weekday time-of-day penalties (before 7am, 7am–6pm, after 6pm; Friday late ×1.15)
- Calculates overtime multipliers (×1.50 first 3 hrs, ×2.00 beyond) on top of time-of-day penalties
- Enforces casual minimum engagement (3 hours) with padding at the shift’s day-type rate and warnings
- Validates penalty rates (Saturday/Sunday consistency, Sunday ×1.50); overrides Sunday when using casual loading for rate
- Calculates costs per segment using the penalty map
- Adds wage and expense allowances (percentage wage allowances use the same effective hourly rate as the shift)
- Sums all segments and allowances for total shift/roster cost
- Returns segments, allowances, total cost, total hours, and warnings
8. Recent Bug Fixes (February 2026)
The following bugs were identified and fixed in a code review:
- BUG 1: Weekday time-of-day penalties now correctly applied (before 7am, 7am-6pm, after 6pm)
- BUG 2: Weekday overtime logic implemented (max 9 ordinary hours, then ×1.50/×2.00 multipliers)
- BUG 3: Casual minimum engagement enforced (3 hours minimum with padding at day-type rate)
- BUG 4: Saturday penalty rate verification; MA000004 CA Level 1 uses flat ×1.25 (no tiered Saturday)
- BUG 5: Sunday rate multiplier validation (×1.50); Sunday overridden to ordinary×1.50 when using casual loading for rate
- BUG 6: Casual loading: user-setting “Casual loading %” (0–100, default 25) applied when user selects Casual and classification has weekly base; 0% respected (no longer treated as 25%)
Additional behaviour: “Calculated rate” in the UI reflects the same effective rate used in shift cost when Casual + weekly base; percentage-based wage allowances use that same effective hourly rate. All fixes include comprehensive unit tests in tests/shift-cost-bugs.test.js.
Documentation generated for Award Interpreter system
Last updated: February 2026