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.

Builders are the structural assembly layer of Shiftly’s domain. Each builder receives raw inputs — a shift, a day’s accumulated shifts, or a month’s calendar metadata — and produces a typed data structure suitable for reducers or UI consumption. Builders are deliberately free of business logic: they delegate segment classification to resolvers and pay computation to calculators, keeping their own responsibility narrowly scoped to orchestrating that delegation and combining the results into the correct output shape.

The Builder Interface

Every builder in the domain implements the same single-method contract from core-behaviors.ts:
export interface Builder<Input, Output> {
  build(params: Input): Output;
}
There are no side effects, no async operations, and no internal state mutations. Given the same input, a builder always produces the same output — making the entire construction pipeline deterministic and straightforward to unit-test.
Builders construct data structures only — they never embed pay rate logic or calendar rules. Salary bracket computation is delegated to calculators, and day-type classification is delegated to resolvers. This separation ensures that fixing a pay rule or adding a new holiday type requires changing exactly one class.

Builders Overview

ShiftSegmentBuilder

Splits a shift into labeled time segments by delegating to ShiftSegmentResolver, handling cross-midnight shifts and DST offsets.

ShiftMapBuilder

Combines segment labels with per-shift regular, extra, and special calculators to produce a complete ShiftPayMap.

DayPayMapBuilder

Aggregates multiple ShiftPayMaps for a single day and computes daily per-diem and meal allowance.

WorkDaysForMonthBuilder

Iterates over all days in a month, classifies each using holiday event data, and sets cross-day continuation flags.

ShiftSegmentBuilder

ShiftSegmentBuilder is the first builder invoked for any shift. It takes a Shift and its WorkDayMeta context and returns an ordered list of LabeledSegmentRange values — time windows with a pay rate key attached to each window.

Signature

class ShiftSegmentBuilder implements Builder<
  { shift: Shift; meta: WorkDayMeta },
  LabeledSegmentRange[]
>

Type Reference

export interface Point {
  start: number; // minutes from midnight (day-start)
  end: number;   // minutes from midnight
}

export interface LabeledSegmentRange {
  point: Point;
  percent: number; // e.g. 0.2, 0.5, 1.0, 1.5, 2.0
  key: string;     // "hours20" | "hours50" | "hours100" | "shabbat150" | "shabbat200"
}

export interface WorkDayMeta {
  date: string;                     // ISO date string, e.g. "2024-09-13"
  typeDay: WorkDayType;             // Regular | SpecialPartialStart | SpecialFull
  crossDayContinuation: boolean;    // true when the previous night was a special day
  holidayKey?: HolidayKey;          // optional holiday identifier
}

How It Works

  1. Validates the shift — calls shiftService.isValidShiftDuration(shift); returns [] for zero-duration or inverted shifts.
  2. Splits on midnight — computes end minutes relative to the start date using ShiftService.getMinutesFromMidnight. If the shift extends beyond 30:00 (06:00 the next day), it creates a secondPart point.
  3. Resolves each part — delegates each Point to ShiftSegmentResolver.resolve({ point, meta }), which maps the point against the correct time-window map for Regular, SpecialPartialStart, or SpecialFull days.
  4. Handles the second day — the second part uses a synthetic metaNextDay where typeDay is set to SpecialFull if meta.crossDayContinuation is true, otherwise Regular. All resulting segment points are shifted by +1440 minutes so they sit in a continuous coordinate space.
  5. Merges and sorts — combines both part arrays and sorts by point.start.

DST Awareness

The special-day start time (Friday/eve-of-holiday) is either 17:00 or 18:00 depending on whether the system clock’s UTC offset is −3h (Israel Summer Time) or −2h (Winter Time). ShiftSegmentResolver reads new Date(date).getTimezoneOffset() at resolve time, so DST transitions are handled correctly without any manual configuration.

DefaultShiftMapBuilder

DefaultShiftMapBuilder takes a validated shift and produces a ShiftPayMap — the complete pay breakdown for one shift including regular brackets, evening/night bonuses, special-day hours, and per-diem metadata.

Signature

class DefaultShiftMapBuilder implements ShiftMapBuilder

// ShiftMapBuilder is:
type ShiftMapBuilder = Builder<
  {
    shift: Shift;
    meta: WorkDayMeta;
    standardHours: number;
    isFieldDutyShift: boolean;
  },
  ShiftPayMap
>

Output Type

// shift level
export interface ShiftPayMap extends BasePayMap {
  perDiemShift: { isFieldDutyShift: boolean; hours: number };
}

export interface BasePayMap {
  regular: RegularBreakdown;
  extra: ExtraBreakdown;
  special: SpecialBreakdown;
  totalHours: number;
}

Construction Logic

build(params: {
  shift: Shift;
  meta: WorkDayMeta;
  standardHours: number;
  isFieldDutyShift: boolean;
}): ShiftPayMap
  1. Calls segmentBuilder.build({ shift, meta }) to get LabeledSegmentRange[].
  2. Computes totalHours via shiftService.getDurationShift(shift).
  3. Passes segments to extraCalculator.calculate(labeledSegments)ExtraBreakdown.
  4. Passes segments to specialCalculator.calculate(labeledSegments)SpecialBreakdown.
  5. Subtracts special hours from totalHours to get regularHours (Shabbat hours are not double-counted in the regular bracket).
  6. Passes { totalHours: regularHours, standardHours, meta } to regularCalculator.calculate(...)RegularBreakdown.
  7. Constructs and returns the ShiftPayMap.

DefaultDayPayMapBuilder

DefaultDayPayMapBuilder aggregates all ShiftPayMaps recorded for a single calendar day into a unified WorkDayMap. It also handles sick and vacation days, computes per-diem eligibility, and determines meal allowance.

Signature

class DefaultDayPayMapBuilder implements DayPayMapBuilder

// DayPayMapBuilder is:
type DayPayMapBuilder = Builder<
  {
    shifts: ShiftPayMap[];
    status: WorkDayStatus;     // normal | sick | vacation
    meta: WorkDayMeta;
    standardHours: number;
    year: number;
    month: number;
  },
  WorkDayMap
>

Output Type

// day level
export interface WorkDayMap {
  workMap: BasePayMap;
  hours100Sick: Segment;
  hours100Vacation: Segment;
  extra100Shabbat: Segment;
  perDiem: DailyPerDiemInfo;
  mealAllowance: MealAllowance;
  totalHours: number;
}

Construction Logic

  1. Accumulates extra and special breakdowns across all shifts using the ExtraCalculator and SpecialCalculator reducers.
  2. Deducts total Shabbat hours (shabbat150 + shabbat200) from totalHours before passing to RegularByDayCalculator.
  3. Calls calculatePerDiem(perDiemShifts, year, month) — resolves the applicable historical rate via TimelinePerDiemRateResolver before computing.
  4. Classifies meal allowance day info: night hours are the sum of hours50 and shabbat200. A day is considered a “meaningful night” shift only if night hours ≥ 4.
  5. Calls MealAllowanceResolver.resolve({ day, rates }) to get MealAllowance.
  6. Sets hours100Sick and hours100Vacation to zero-hour segments; sets extra100Shabbat to the total Shabbat hours.

DefaultWorkDaysForMonthBuilder

DefaultWorkDaysForMonthBuilder iterates over every day in a given year/month, classifies it using holiday event data fetched from Hebcal, and constructs a WorkDayInfo[] array that forms the calendar skeleton for the month.

Signature

class DefaultWorkDaysForMonthBuilder implements WorkDaysForMonthBuilder

// WorkDaysForMonthBuilder is:
type WorkDaysForMonthBuilder = Builder<
  {
    year: number;
    month: number;
    eventMap: Record<string, string[]>; // ISO date → array of Hebcal event titles
  },
  WorkDayInfo[]
>

Output Element Type

export interface WorkDayInfo {
  meta: WorkDayMeta;   // date, typeDay, crossDayContinuation, holidayKey
  hebrewDay: string;   // e.g. "א", "ב", ... "ש"
}

Construction Logic

For each calendar day in the month:
  1. Looks up eventMap[formattedDate] to get the array of Hebcal event titles for that date.
  2. Calls holidayResolver.resolve({ weekday, eventTitles }) to determine WorkDayType:
    • Saturday or a paid holiday → SpecialFull
    • Friday or an eve-of-holiday event → SpecialPartialStart
    • Otherwise → Regular
  3. Resolves the holidayKey from the event titles using a prefix-match against hebrewHolidayNames.
  4. Sets crossDayContinuation on day n-1 to true when day n is SpecialFull — this tells the shift builder that a shift ending the following morning is still in Shabbat/holiday time.
  5. After all days are processed, checks the first day of the next month to correctly set the last day’s crossDayContinuation flag.
The crossDayContinuation flag is set on the previous day, not the special day itself. A Friday shift that ends at 02:00 Saturday morning must be split: the Saturday portion carries SpecialFull rates. ShiftSegmentBuilder reads this flag from the current day’s WorkDayMeta to construct the correct metaNextDay.

Build docs developers (and LLMs) love