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.

Resolvers are the context-reading layer of Shiftly’s domain. While calculators apply fixed arithmetic rules and builders assemble data structures, resolvers answer questions that require external or historical context: Is this calendar date a Jewish holiday? What per-diem rate was in effect in March 2023? Does this shift span an evening-to-night boundary? Resolvers encapsulate all of these lookups behind a uniform interface, keeping builders and calculators free of date-handling, API data, and rate table logic.

The Resolver Interface

export interface Resolver<Input, Output> {
  resolve(params: Input): Output;
}
Like calculators, resolvers are pure with respect to their domain inputs — no Redux, no React, no side effects. Historical rate resolvers maintain internal timeline tables rather than making live API calls at resolve time.

Resolvers Overview

ShiftSegmentResolver

Maps a time-window Point against the correct rate map for the day’s WorkDayType, returning labeled segment ranges.

HolidayResolverService

Classifies each calendar day as Regular, SpecialPartialStart, or SpecialFull from its weekday and Hebcal event titles.

WorkDayInfoResolver

Inspects a WorkDayInfo record for special-day status, partial-holiday start, and cross-day continuation flags.

DefaultMonthResolver

Provides available months for a year, resolves the default month to display, and returns Hebrew month names.

TimelinePerDiemRateResolver

Looks up the applicable per-diem rate (ILS per point) for a given year and month from a historical timeline.

TimelineMealAllowanceRateResolver

Looks up large and small meal allowance rates for a given year and month from a historical timeline.

MealAllowanceResolver

Determines meal allowance eligibility and computes the monetary entitlement for a single day.

ShiftSegmentResolver

ShiftSegmentResolver is the innermost rate-mapping component. It takes a time-window (Point) and the current day’s WorkDayMeta, selects the correct rate map based on typeDay, and returns only the segments that overlap the requested window.
class ShiftSegmentResolver implements Resolver<
  { point: Point; meta: WorkDayMeta },
  LabeledSegmentRange[]
>

Key Types

export interface Point {
  start: number; // minutes from midnight
  end: number;
}

export interface LabeledSegmentRange {
  point: Point;
  percent: number;
  key: string; // "hours20" | "hours50" | "hours100" | "shabbat150" | "shabbat200"
}

export interface WorkDayMeta {
  date: string;
  typeDay: WorkDayType;              // Regular | SpecialPartialStart | SpecialFull
  crossDayContinuation: boolean;
  holidayKey?: HolidayKey;
}

Rate Maps by Day Type

WindowKeyRate
00:00 – 06:00hours5050% night bonus
06:00 – 17:00hours100Standard
14:00 – 22:00hours2020% evening bonus
22:00 – 30:00hours5050% night bonus

Segment Intersection

The resolver’s findSegments method trims each rate-map segment to the intersection with the requested Point:
private findSegments(target: Point, src: LabeledSegmentRange[]): LabeledSegmentRange[] {
  // Locate the first and last overlapping segments, then clip start/end to target bounds.
  // Segments with zero net duration are discarded.
}
Only segments that actually overlap the shift’s time window are returned — segments before target.start or after target.end are excluded entirely.

HolidayResolverService

HolidayResolverService is the bridge between raw Hebcal API event titles and Shiftly’s WorkDayType classification. It maintains internal lists of paid holidays and partial-start events (eves and half-days).
class HolidayResolverService implements HolidayResolver

// HolidayResolver = Resolver<
//   { weekday: number; eventTitles: string[] },
//   WorkDayType
// >

Classification Rules

resolve(params: { weekday: number; eventTitles: string[] }): WorkDayType
ConditionResult
weekday === SaturdaySpecialFull
eventTitles contains a paid holidaySpecialFull
eventTitles contains a partial-start eventSpecialPartialStart
weekday === FridaySpecialPartialStart
OtherwiseRegular
Rosh Hashana, Rosh Hashana II, Yom Kippur, Sukkot I,
Shmini Atzeret, Pesach I, Pesach VII, Yom HaAtzma'ut, Shavuot I

Partial Start Events (Eve / Half-Day)

Erev Rosh Hashana, Erev Yom Kippur, Erev Sukkot, Erev Pesach,
Erev Shavuot, Yom HaZikaron, Sukkot VII (Hoshana Rabba),
Pesach VI (CH'M)
HolidayResolverService is used by DefaultWorkDaysForMonthBuilder during calendar assembly. It does not fetch from Hebcal directly — the eventMap (ISO date → event titles) is fetched upstream and passed in as a builder parameter.

WorkDayInfoResolver

WorkDayInfoResolver is a thin utility resolver that exposes named predicates for inspecting a WorkDayInfo record. It is used by DefaultWorkDaysForMonthBuilder to set crossDayContinuation on each day.
class WorkDayInfoResolver implements DayInfoResolver

export interface DayInfoResolver {
  isSpecialFullDay(day: WorkDayInfo): boolean;
  isPartialHolidayStart(day: WorkDayInfo): boolean;
  hasCrossDayContinuation(day: WorkDayInfo): boolean;
  formatHebrewWorkDay(day: WorkDayInfo): string;
}

Methods

MethodReturns
isSpecialFullDay(day)day.meta.typeDay === WorkDayType.SpecialFull
isPartialHolidayStart(day)day.meta.typeDay === WorkDayType.SpecialPartialStart
hasCrossDayContinuation(day)day.meta.crossDayContinuation === true
formatHebrewWorkDay(day)Hebrew day letter + zero-padded day number, e.g. "ו-13"

DefaultMonthResolver

DefaultMonthResolver provides the application with the set of months available for a given year, the default month to display on load, and Hebrew month names. It respects the system’s effective start date (November 2015) and does not expose future months.
class DefaultMonthResolver implements MonthResolver

export interface MonthResolver {
  getAvailableMonths(year: number): number[];
  getAvailableMonthOptions(year: number): { value: number; label: string }[];
  resolveDefaultMonth(year: number): number;
  getMonthName(monthIndex: number): string;
  getAllMonthNames(): string[];
  getCurrentYear(): number;
}

Availability Rules

YearAvailable months
Before 2015None
2015 (system start)November – December (indices 10–11)
Past yearAll 12 months (indices 0–11)
Current yearJanuary through current month
Future yearNone
resolveDefaultMonth returns the current month for the current year, November for 2015, and January for all other past years.

Timeline Per-Diem and Meal Allowance Rate Resolvers

Both TimelinePerDiemRateResolver and TimelineMealAllowanceRateResolver maintain an internal sorted timeline of rate changes. When resolved for a (year, month) pair, they return the most recent entry that is ≤ the requested period.

TimelinePerDiemRateResolver

class TimelinePerDiemRateResolver implements Resolver<
  { year: number; month: number },
  number  // ILS per point (Tier A rate)
>
Internal timeline (as of the current source):
Effective fromRate A (ILS)
2000-0133.90
2024-0936.30

TimelineMealAllowanceRateResolver

class TimelineMealAllowanceRateResolver implements Resolver<
  { year: number; month: number },
  MealAllowanceRates
>

export interface MealAllowanceRates {
  small: number; // ILS per small meal allowance point
  large: number; // ILS per large meal allowance point
}
Internal timeline (as of the current source):
Effective fromSmall (ILS)Large (ILS)
2000-0113.5019.70
2024-0914.5021.10

Resolution Algorithm

Both resolvers filter timeline entries to those with year < requested or (year === requested && month <= requested), sort descending by date, and return the first match’s value. If no entry matches, the default is 0 (per-diem) or { small: 0, large: 0 } (meal allowance).

MealAllowanceResolver

MealAllowanceResolver is the decision gate for daily meal allowance entitlement. It coordinates LargeMealAllowanceCalculator and SmallMealAllowanceCalculator and ensures that large and small allowances are mutually exclusive: only one can be awarded per day.
class MealAllowanceResolver implements Resolver<
  { day: MealAllowanceDayInfo; rates: MealAllowanceRates },
  MealAllowance
>

Logic

resolve(params: { day: MealAllowanceDayInfo; rates: MealAllowanceRates }): MealAllowance {
  const large = largeCalculator.calculate({ day, rate: rates.large });

  if (large.points > 0) {
    // Large wins — small is zeroed
    return { large, small: { points: 0, amount: 0 } };
  }

  const small = smallCalculator.calculate({ day, rate: rates.small });
  return { large: { points: 0, amount: 0 }, small };
}
The large calculator is always evaluated first. If it awards a point, the small calculator is never called — avoiding double-counting on long overnight shifts. MealAllowanceResolver also provides createEmpty(): MealAllowance which returns { large: { points: 0, amount: 0 }, small: { points: 0, amount: 0 } }, used by DefaultDayPayMapBuilder when initializing the day state.

Build docs developers (and LLMs) love