SCG — Travel Expense Test Cases
Test cases for verifying the SCG computed values: Time (duration in hours, in UTC), Location (regulated rates), and the expense amount produced by the SCG formula.
Formula reminder: with duration
Hhours,fullDays = floor(H / 24)is charged at the full-day rate; the leftoverremainderHours = H − fullDays·24is charged0if< 5h, the half-day rate if5–<12h, and the full-day rate if≥ 12h.
Model reminder: all leg-to-leg comparisons (ordering, same-day handoff) are evaluated in UTC; the UI shows/accepts the local time of the location. A later leg's
startTimemust be≥the previous leg'sendTime(UTC). Two legs link into a connected chain only when one ends on the same UTC calendar day the next begins. A chain's duration is the sum of each leg's duration — gaps between legs are not counted.
Unless stated otherwise, the examples use a single location with full-day rate 190,000 and half-day rate 95,000.
Group 1 — Single Leg (Standalone)
Exercises each branch of the remainder rule. A standalone leg has no same-day handoff.
TC-01 — Remainder below the floor (< 5h)
Input:
| Leg | Location | Start (UTC) | End (UTC) | Duration |
|---|---|---|---|---|
| A | X | Jun 1, 08:00 | Jun 1, 12:00 | 4h |
Expected output:
| Step | Value |
|---|---|
H | 4h |
fullDays | 0 |
remainderHours | 4h → < 5h → drop |
| Amount | 0 |
TC-02 — Remainder in the half-day band (5–<12h)
Input:
| Leg | Location | Start (UTC) | End (UTC) | Duration |
|---|---|---|---|---|
| A | X | Jun 1, 08:00 | Jun 1, 16:00 | 8h |
Expected output:
| Step | Value |
|---|---|
H | 8h |
fullDays | 0 |
remainderHours | 8h → 5–<12h → half-day |
| Amount | 0 + 95,000 = 95,000 |
TC-03 — Remainder at/above the full-day threshold (≥ 12h)
Input:
| Leg | Location | Start (UTC) | End (UTC) | Duration |
|---|---|---|---|---|
| A | X | Jun 1, 06:00 | Jun 1, 19:00 | 13h |
Expected output:
| Step | Value |
|---|---|
H | 13h |
fullDays | 0 |
remainderHours | 13h → ≥ 12h → full-day |
| Amount | 0 + 190,000 = 190,000 |
TC-04 — Whole day plus a half-day remainder
Input:
| Leg | Location | Start (UTC) | End (UTC) | Duration |
|---|---|---|---|---|
| A | X | Jun 1, 08:00 | Jun 2, 15:00 | 31h |
Expected output:
| Step | Value |
|---|---|
H | 31h |
fullDays | 1 (24h) |
remainderHours | 7h → 5–<12h → half-day |
| Amount | 190,000 + 95,000 = 285,000 |
TC-05 — Multiple whole days plus a full-day remainder
Input:
| Leg | Location | Start (UTC) | End (UTC) | Duration |
|---|---|---|---|---|
| A | X | Jun 1, 08:00 | Jun 3, 21:00 | 61h |
Expected output:
| Step | Value |
|---|---|
H | 61h |
fullDays | 2 (48h) |
remainderHours | 13h → ≥ 12h → full-day |
| Amount | 2 × 190,000 + 190,000 = 570,000 |
TC-06 — Exact whole day (zero remainder)
Input:
| Leg | Location | Start (UTC) | End (UTC) | Duration |
|---|---|---|---|---|
| A | X | Jun 1, 08:00 | Jun 2, 08:00 | 24h |
Expected output:
| Step | Value |
|---|---|
H | 24h |
fullDays | 1 |
remainderHours | 0h → < 5h → drop |
| Amount | 190,000 + 0 = 190,000 |
Group 2 — Connected Chains (Same-Day Handoff, UTC)
Consecutive legs where one leg ends on the same UTC calendar day the next begins. Chain duration = sum of each leg's duration.
TC-07 — Two legs, same UTC day, gap not counted
Input:
| Leg | Location | Start (UTC) | End (UTC) | Duration |
|---|---|---|---|---|
| A | X | Jun 1, 08:00 | Jun 1, 12:00 | 4h |
| B | X | Jun 1, 15:00 | Jun 1, 22:00 | 7h |
Handoff: A ends Jun 1, B begins Jun 1 (same UTC day) → one connected chain.
Expected output (connected chain):
| Step | Value | Working |
|---|---|---|
H | 11h | 4h + 7h (the 12:00→15:00 gap is not counted) |
fullDays | 0 | |
remainderHours | 11h → 5–<12h → half-day | |
| Amount | 95,000 |
TC-08 — Multi-day chain over consecutive handoffs
Input:
| Leg | Location | Start (UTC) | End (UTC) | Duration |
|---|---|---|---|---|
| A | X | Jun 1, 20:00 | Jun 2, 06:00 | 10h |
| B | X | Jun 2, 09:00 | Jun 3, 11:00 | 26h |
Handoff: A ends Jun 2, B begins Jun 2 (same UTC day) → connected chain.
Expected output (connected chain):
| Step | Value | Working |
|---|---|---|
H | 36h | 10h + 26h |
fullDays | 1 (24h) | |
remainderHours | 12h → ≥ 12h → full-day | |
| Amount | 190,000 + 190,000 = 380,000 |
Group 3 — Independent Legs (No Same-Day Handoff)
Legs that do not share a UTC handoff day are evaluated separately, each with its own formula.
TC-09 — Adjacent UTC days, no shared day → not linked
A ends Jun 1 (UTC); B begins Jun 2 (UTC) — different UTC days, only 2h apart → not linked.
Input:
| Leg | Location | Start (UTC) | End (UTC) | Duration |
|---|---|---|---|---|
| A | X | Jun 1, 06:00 | Jun 1, 23:00 | 17h |
| B | X | Jun 2, 01:00 | Jun 2, 09:00 | 8h |
Expected output (two independent legs):
| Leg | H | fullDays | Remainder | Amount |
|---|---|---|---|---|
| A | 17h | 0 | 17h → full-day | 190,000 |
| B | 8h | 0 | 8h → half-day | 95,000 |
No linking occurs — proximity in clock time does not matter, only a shared UTC calendar day does.
Group 4 — Timezone & Ordering (UTC vs. Local)
The non-overlap and handoff checks run on UTC; the UI displays local time. These cases show where the two disagree.
TC-10 — Looks fine locally, overlaps in UTC → rejected
Input (as entered on the UI, local time):
| Leg | Location (TZ) | Local start | Local end | → UTC start | → UTC end |
|---|---|---|---|---|---|
| A | Tokyo (UTC+9) | Jun 2, 08:00 | Jun 2, 08:00 | Jun 1, 23:00 | Jun 1, 23:00 |
| B | Bangkok (UTC+7) | Jun 2, 05:00 | Jun 2, 13:00 | Jun 1, 22:00 | Jun 2, 06:00 |
Expected output: Invalid ordering. In local time B (05:00) appears to precede A's end (08:00) on the same day, but in UTC B.start = Jun 1 22:00 < A.end = Jun 1 23:00 → overlap → the constraint leg[n+1].startTime ≥ leg[n].endTime (UTC) fails. The system rejects the itinerary.
TC-11 — Same-day handoff decided in UTC, not local
Input:
| Leg | Location (TZ) | Local start | Local end | → UTC start | → UTC end |
|---|---|---|---|---|---|
| A | Bangkok (UTC+7) | Jun 2, 05:00 | Jun 2, 05:00 | Jun 1, 22:00 | Jun 1, 22:00 |
| B | Bangkok (UTC+7) | Jun 2, 07:00 | Jun 2, 15:00 | Jun 2, 00:00 | Jun 2, 08:00 |
Expected output: Not a connected chain. Both legs are on Jun 2 locally, but in UTC A ends Jun 1 and B begins Jun 2 — different UTC calendar days → no same-day handoff. The legs are evaluated independently (A: 0h → 0; B: 8h → half-day → 95,000).
Mirror case: legs on two different local days can still share one UTC day and therefore link. The UTC day is always the deciding factor.
Group 5 — Connected Chain Across Different Locations (pending decision)
The handling of a chain whose legs carry different location rates is not yet finalised. These cases show the result under Option A (time-attributed per day) — the recommended-but-unconfirmed approach in the rules doc.
Status: proposed. Expected values below assume Option A. They will need revisiting if Option B or C is chosen.
Rates used here: Location X full 190,000 / half 95,000; Location Y full 300,000 / half 150,000.
TC-12 — Whole day in X, remainder day in Y (Option A)
Input:
| Leg | Location | Start (UTC) | End (UTC) | Duration |
|---|---|---|---|---|
| A | X | Jun 1, 08:00 | Jun 2, 08:00 | 24h |
| B | Y | Jun 2, 10:00 | Jun 2, 23:00 | 13h |
Handoff: A ends Jun 2, B begins Jun 2 → connected chain. Chain H = 24 + 13 = 37h.
Expected output (Option A — per-day attribution):
| Unit | Window | Location occupied | Rate applied | Amount |
|---|---|---|---|---|
| Whole day 1 | first 24h (in A) | X | full-day | 190,000 |
| Remainder | 13h → ≥ 12h → full-day (in B) | Y | full-day | 300,000 |
| Total | 490,000 |
Under Option B (final destination = Y) the whole
37hwould instead bill at Y's rates (300,000 + 300,000 = 600,000). Recorded here to make the open decision concrete.
Group 6 — Multiple Travellers on the Same Trip
Travellers are calculated completely independently — no time or location factor is shared.
TC-13 — Two travellers, different durations and locations
Input:
| Traveller | Leg | Location | Start (UTC) | End (UTC) | Duration |
|---|---|---|---|---|---|
| Alice | A | X | Jun 1, 08:00 | Jun 2, 15:00 | 31h |
| Bob | B | X | Jun 1, 09:00 | Jun 1, 13:00 | 4h |
Expected output:
| Traveller | H | fullDays | Remainder | Amount |
|---|---|---|---|---|
| Alice | 31h | 1 | 7h → half-day | 285,000 |
| Bob | 4h | 0 | 4h → drop | 0 |
Bob's short leg has no effect on Alice's calculation, and vice versa.
TC-14 — One traveller has a chain, the other a standalone leg
Input:
| Traveller | Leg | Location | Start (UTC) | End (UTC) | Duration |
|---|---|---|---|---|---|
| Alice | A | X | Jun 1, 08:00 | Jun 1, 12:00 | 4h |
| Alice | B | X | Jun 1, 15:00 | Jun 1, 22:00 | 7h |
| Bob | C | X | Jun 1, 06:00 | Jun 2, 18:00 | 36h |
Alice: A→B same UTC day → connected chain (H = 11h). Bob: standalone (H = 36h).
Expected output:
| Traveller | Chain/Leg | H | fullDays | Remainder | Amount |
|---|---|---|---|---|---|
| Alice | A + B (chain) | 11h | 0 | 11h → half-day | 95,000 |
| Bob | C (standalone) | 36h | 1 | 12h → full-day | 380,000 |
Group 7 — Edge Cases
TC-15 — Single traveller, no legs
Input: No legs assigned.
Expected output: No calculation performed. No expense applied.
TC-16 — Ordering violation (overlap in UTC)
Input:
| Leg | Location | Start (UTC) | End (UTC) |
|---|---|---|---|
| A | X | Jun 1, 08:00 | Jun 1, 14:00 |
| B | X | Jun 1, 12:00 | Jun 1, 18:00 |
Expected output: Invalid. B.start = 12:00 < A.end = 14:00 (UTC) → the legs overlap, violating leg[n+1].startTime ≥ leg[n].endTime. The itinerary is rejected; no amount is computed.
Summary Table
| TC | Travellers | Legs | Linked? | H | Remainder rule | Amount |
|---|---|---|---|---|---|---|
| TC-01 | 1 | 1 | Standalone | 4h | drop (<5h) | 0 |
| TC-02 | 1 | 1 | Standalone | 8h | half (5–<12h) | 95,000 |
| TC-03 | 1 | 1 | Standalone | 13h | full (≥12h) | 190,000 |
| TC-04 | 1 | 1 | Standalone | 31h | half | 285,000 |
| TC-05 | 1 | 1 | Standalone | 61h | full | 570,000 |
| TC-06 | 1 | 1 | Standalone | 24h | drop (0h) | 190,000 |
| TC-07 | 1 | 2 | Chain (same UTC day) | 11h | half | 95,000 |
| TC-08 | 1 | 2 | Chain (handoff) | 36h | full | 380,000 |
| TC-09 | 1 | 2 | Independent (diff. UTC day) | 17h / 8h | full / half | 190,000 / 95,000 |
| TC-10 | 1 | 2 | — | — | — | Rejected (UTC overlap) |
| TC-11 | 1 | 2 | Independent (diff. UTC day) | 0h / 8h | drop / half | 0 / 95,000 |
| TC-12 | 1 | 2 | Chain, mixed location | 37h | per-day (Option A) | 490,000 ⚠️ pending |
| TC-13 | 2 | 1 each | Independent | 31h / 4h | half / drop | 285,000 / 0 |
| TC-14 | 2 | 2 / 1 | Chain / Standalone | 11h / 36h | half / full | 95,000 / 380,000 |
| TC-15 | 1 | 0 | N/A | N/A | N/A | N/A |
| TC-16 | 1 | 2 | — | — | — | Rejected (UTC overlap) |