Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/dmaman86/shiftly/llms.txt

Use this file to discover all available pages before exploring further.

Shiftly follows a strict three-level pipeline to turn raw shift timestamps into a fully-detailed pay breakdown. Every hour worked is classified by its pay rate, bonuses are layered on top, special-day premiums are applied where required, and everything is accumulated into daily and then monthly totals — all in accordance with Israeli labor law.

The Three-Level Pipeline

The calculation flows top-down through three nested scopes: shift → day → month. Each scope produces a well-typed data structure that the next scope consumes.
1

Shift: raw hours → ShiftPayMap

A single Shift (start date/time + end date/time) is first split into labeled time segments by the ShiftSegmentResolver. Each segment carries a key (hours100, hours20, shabbat150, etc.) and a minute-resolution Point. The three calculators — RegularByShiftCalculator, ExtraCalculator, and SpecialCalculator — consume these segments and produce a ShiftPayMap.
2

Day: multiple ShiftPayMaps → WorkDayMap

All shifts that belong to the same calendar day are accumulated into a single WorkDayMap. The RegularByDayCalculator re-calculates the 100/125/150% brackets using the day’s total hours so that overtime thresholds are applied once per day, not once per shift. Per-diem and meal-allowance information is also resolved at the day level.
3

Month: all WorkDayMaps → MonthPayMap

Every day in the selected month is reduced into a single MonthPayMap. Sick and vacation days contribute their own hours100Sick and hours100Vacation segments, and per-diem points and amounts are summed across all field-duty days.

Data Flow Diagram

┌─────────────────────────────────────────────────────────────┐
│  INPUT: Shift { start, end, isDuty }                        │
└────────────────────────┬────────────────────────────────────┘


          ┌──────────────────────────┐
          │   ShiftSegmentResolver   │  labels each minute-range
          │   (WorkDayType routing)  │  with a pay-rate key
          └──────────────┬───────────┘
                         │  LabeledSegmentRange[]

     ┌───────────────────────────────────────┐
     │          Calculators                  │
     │  ┌─────────────────────────────────┐  │
     │  │ RegularByShiftCalculator        │  │  → RegularBreakdown
     │  │ ExtraCalculator                 │  │  → ExtraBreakdown
     │  │ SpecialCalculator               │  │  → SpecialBreakdown
     │  └─────────────────────────────────┘  │
     └───────────────────────┬───────────────┘
                             │  ShiftPayMap

          ┌──────────────────────────────────┐
          │      DayPayMapBuilder            │  accumulates shifts;
          │  (RegularByDayCalculator)        │  re-brackets per-day totals
          └──────────────────┬───────────────┘
                             │  WorkDayMap

          ┌──────────────────────────────────┐
          │   WorkDaysForMonthBuilder        │  iterates calendar days
          │   + MonthPayMapReducer           │  sums into monthly totals
          └──────────────────┬───────────────┘
                             │  MonthPayMap

                    ┌────────────────┐
                    │  UI / Report   │
                    └────────────────┘

Core Type: Segment

Every individual hour bucket in the system is represented by a Segment. It always carries both the pay-rate multiplier and the number of hours accumulated in that bucket:
interface Segment {
  percent: number; // pay-rate multiplier, e.g. 1.25 for 125%
  hours: number;   // hours accumulated in this bucket
}

The BasePayMap Interface

All three pipeline scopes share a common structure for their pay data:
interface BasePayMap {
  regular: RegularBreakdown;  // 100%, 125%, 150% buckets
  extra: ExtraBreakdown;      // 20% evening and 50% night bonuses
  special: SpecialBreakdown;  // 150% and 200% Shabbat/holiday rates
  totalHours: number;         // sum of all worked hours in this scope
}
The ShiftPayMap extends BasePayMap with a per-diem field:
interface ShiftPayMap extends BasePayMap {
  perDiemShift: {
    isFieldDutyShift: boolean;
    hours: number;
  };
}
The WorkDayMap wraps the base pay data alongside leave and allowance fields:
interface WorkDayMap {
  workMap: BasePayMap;
  hours100Sick: Segment;
  hours100Vacation: Segment;
  extra100Shabbat: Segment;
  perDiem: DailyPerDiemInfo;
  mealAllowance: MealAllowance;
  totalHours: number;
}
The MonthPayMap flattens everything into a single summary object:
interface MonthPayMap {
  hours100Sick: Segment;
  hours100Vacation: Segment;
  extra100Shabbat: Segment;
  perDiem: PerDiemInfo;
  mealAllowance: MealAllowance;
  regular: RegularBreakdown;
  extra: ExtraBreakdown;
  special: SpecialBreakdown;
  totalHours: number;
}

Day-Type Classification

Before any calculation runs, each calendar day is classified into one of three types by the HolidayResolverService. The type controls which segment map ShiftSegmentResolver uses:
WorkDayTypeDescription
RegularOrdinary weekday (Sun–Thu)
SpecialPartialStartFriday or a holiday eve — regular until ~17:00/18:00, then special rates
SpecialFullShabbat (Saturday) or a paid public holiday — 150%/200% all day
The crossDayContinuation flag in WorkDayMeta indicates that the current day’s hours are a continuation of a shift that started on the previous calendar day. This prevents double-applying the SpecialFull rate upgrade.

Explore the Sub-Topics

Regular Hours

How the 100%, 125%, and 150% overtime brackets are calculated per shift and per day.

Overtime Bonuses

The additive 20% evening and 50% night bonuses applied to hours in specific time windows.

Special Days

Shabbat, Jewish holidays, and partial special days with 150% and 200% pay rates.

Per-Diem & Meals

Per-diem tier logic (A/B/C) and meal allowance (small/large) with historical rate timelines.

Build docs developers (and LLMs) love