wiki

Report Wizard's Per Diem - Operator Setup Guide

0. Introduction

The Universal Per Diem template is a single, configurable Twig template that calculates per diem allowances for crew based on their duties and away-from-base periods, with support for multiple rate systems, day-counting methods, currency display, and qualification rules. All operator-specific behavior is controlled by configuration flags — no code changes are needed.

Obtaining the report

The report is available under reports Shared by Leon - however, this is only a demonstration version, and its' code cannot be modified. To start adapting the code to your company’s needs, you need to copy the report to Your Saved Reports:

  1. Enter the report,

    image-20260703-152312.png


  2. Select Edit Data,

    image-20260703-152400.png


  3. Click Save As,

    image-20260703-152439.png


  4. (Optional) Change the name to your own,

    image-20260703-153118.png



  5. Save and go to Your Saved Reports to modify the report.

    image-20260703-153245.png

1. How to use this guide

Open BODY in the Leon HTML template editor of the report.

image-20260703-153446.png

All configuration flags are at the top of the file in Section A (behavior flags) and Section B (rate data). Change values there to configure the template for your needs. Section 2 below explains the one-time data-scope setup; the remaining sections explain every flag.

image-20260703-153605.png



2. Data Sources and Scope Setup (read first)

The template consumes three Leon data scopes:

Scope

Leon binding

Role

FTL Sector

reports.ftl_sector

Provides one row per sector (leg) with duty-level times, crew identification, home base, rank (position), and route airports — in both UTC and crew base time.

Crew Positionings

reports.crew_positionings

Ground-transport positioning (train / car / taxi / commercial) that is not captured as a flight sector.

Crew Roster

reports.crew_roster

Off, standby, training, and office days.

Things to know about the data:

  • Crew Positionings links to its duty via duty_unique_id. Ground-transport positioning legs are merged back into the duty route using this identifier.

  • Ground-transport positioning is not a flight sector. Those legs come only from the Crew Positionings scope and are merged into the duty route. Do not remove the Crew Positionings scope or ground positioning legs (and ground-only positioning duties) will be missing from routes.

  • No-flight duties (office, ground duty) appear in FTL Sector as activity_type = 'duty' rows and generate no per diem when at home base.

  • Crew rank comes from the FTL Sector position field. Observed values include CPT, FO, CC1, LTC, CPT2, FO-T, JMP. Use these exact codes in rank_rates and crew_positions if rank rates are enabled.


3. Core Calculation Settings

Flag

Type

Default

Description

calculate_away_from_base_periods

boolean

true

Continuous trip tracking vs. individual duty. When true, the template tracks when crew leave home base and return, treating the entire away period (including days off mid-trip) as per-diem-eligible. When false, each duty is processed individually — only actual duty days generate per diem. Change to false for operators who only pay per diem on active duty days, not for the full trip.

calculate_only_FDP_duties

boolean

false

FTL-only vs. FTL + roster duties. When false, the template processes both duty records (from FTL Sector) and crew roster records (training, standby, etc.). When true, only flight-duty-period records generate per diem. Set to true for operators who only pay per diem for actual flight duties, not ground duties or training.

include_positioning_flights

boolean

true

Whether positioning (deadhead) counts. When true, positioning is included — it can open or extend away-from-base periods. Flight deadheads come from FTL Sector (activity_type = 'positioning'); ground transport comes from Crew Positionings. When false, positioning is ignored. Set to false if the operator does not consider positioning as qualifying for per diem.

calculate_domestic_per_diem

boolean

true

Domestic trip handling. When true, all trips generate per diem regardless of destination. When false, trips where all airports are in the crew member's home-base country are excluded. Set to false for operators who only pay per diem for international trips.

trip_qualification_mode

string

'default'

Trip qualification logic. When 'default', a duty must end outside home base to qualify — same-duty round trips (depart home, return home) are excluded. When 'any_duty', any duty with travel qualifies, including same-day round trips. Set to 'any_duty' for operators who pay per diem on any duty that involves travel, even if the crew returns to base the same day.


4. Time and Display

Flag

Type

Default

Description

use_crew_bt

boolean

true

Crew base time vs. UTC. When true, all times (departures, arrivals, duty periods) use the crew's base timezone. When false, times are displayed in UTC. Set to false only if the operator's per diem rules are defined in UTC. Most operators use base time.

use_iata_codes

boolean

true

Airport code format. When true, airports are shown as 3-letter IATA codes (e.g., MMX). When false, airports are shown as 4-letter ICAO codes (e.g., ESMS). Set to false if you prefer ICAO codes in reports.

Note on crew base time: the Crew Roster scope has no base-time fields, so the template infers each crew member's UTC-to-base-time offset from their FTL Sector duty records and applies it to roster timestamps.


5. Rate System (3-Level Cascade)

The template uses a cascading rate system to determine the per diem rate for each crew member. Rates are evaluated in order of priority — the first matching level wins:

  1. Level 1 — Crew rates (use_crew_rates): Per-person rates with date-based validity periods. Highest priority.

  2. Level 2 — Rank rates (use_rank_rates): Rates based on crew rank/position (from the FTL Sector position field).

  3. Level 3 — Location rates (use_two_pass_rates): Rates based on where the crew member is (airport or country). Uses a two-pass calculation where each calendar day gets its own location-based rate.

If no level matches, the default_rate is used as a final fallback.

Toggle Flags

Flag

Type

Default

Description

use_crew_rates

boolean

false

Enable Level 1 (crew-specific rates). When true, the template first checks if the crew member has a personal rate defined in crew_rates. Enable when crew members have individually negotiated per diem rates or when rates vary by person with date-based periods.

use_rank_rates

boolean

false

Enable Level 2 (rank-based rates). When true, the template checks the crew member's rank (from the FTL Sector position field) against rank_rates. Enable when Captains, First Officers, and Cabin Crew have different per diem rates.

use_two_pass_rates

boolean

true

Enable Level 3 (location-based rates). When true, each calendar day of an away period is individually rated based on the crew member's location that day (airport first, then country, then default). Disable only if the operator uses a flat rate regardless of destination.

Rate Data Hashes

crew_rates — Level 1: Per-Crew Rates

  • Type: Hash (object)

  • Default: {} (empty — no crew-specific rates)

  • Format: Each key is a crew code, each value is an array of rate periods.

  • When to use: When individual crew members have personal per diem rates, especially with date-based validity.

Example:

'JSmith': [
    {'rate': 75.0, 'currency': 'usd', 'start_date': '01-01-2025', 'end_date': '30-06-2025'},
    {'rate': 80.0, 'currency': 'usd', 'start_date': '01-07-2025', 'end_date': null}
]

  • start_date / end_date use DD-MM-YYYY format. Set end_date to null for an open-ended rate.

  • Multiple periods allow tracking historical rate changes.

rank_rates — Level 2: Per-Rank Rates

  • Type: Hash (object)

  • Default: {} (empty — no rank-based rates)

  • Format: Each key is a rank code (FTL Sector position value), each value has rate and currency.

  • When to use: When per diem varies by crew position (Captain vs. FO vs. Cabin Crew).

Example:

'CPT': {'rate': 80.0, 'currency': 'eur'},
'FO':  {'rate': 65.0, 'currency': 'eur'},
'CC1': {'rate': 50.0, 'currency': 'eur'}

crew_positions — Rank Hierarchy

  • Type: Hash (object)

  • Default: {} (empty)

  • Format: Each key is a rank code, each value is a priority number (higher = higher priority).

  • When to use: When using rank rates and a crew member might appear with different positions across sectors in the same period. The highest-priority rank is used for rate lookup.

Example:

'CPT': 4,
'LTC': 3,
'FO':  2,
'CC1': 1

airport_rates — Level 3: Airport-Specific Rates

  • Type: Hash (object)

  • Default: {} (empty — no airport-specific rates)

  • Format: Each key is an ICAO airport code, each value has rate and currency. Optionally add tiers for tiered mode.

  • When to use: When specific airports have special per diem rates (e.g., expensive cities).

Example:

'KJFK': {'rate': 100.0, 'currency': 'usd'},
'RJTT': {'rate': 120.0, 'currency': 'usd', 'tiers': {'full': 120, 'half': 75}}

  • Airport rates have highest priority within Level 3.

country_rates — Level 3: Country Rates

  • Type: Hash (object)

  • Default: {} (empty — no country-specific rates)

  • Format: Each key is a 2-character ICAO country prefix, each value has rate and currency. Optionally add tiers for tiered mode.

  • When to use: When per diem varies by destination country but not by specific airport.

Example:

'EG': {'rate': 70.0, 'currency': 'gbp'},
'LF': {'rate': 65.0, 'currency': 'eur'},
'EP': {'rate': 100.0, 'currency': 'eur', 'tiers': {'full': 100, 'half': 62}}

  • Country rates are used when no airport-specific rate matches. Common prefixes: EG=UK, LF=France, ED=Germany, ES=Sweden, EK=Denmark, EP=Poland, LE=Spain, LI=Italy.

default_rate — Final Fallback

  • Type: Object

  • Default: {'rate': 10.0, 'currency': 'eur'}

  • When to use: Always set this. It is the fallback when no crew, rank, airport, or country rate matches. Optionally add tiers for tiered mode.

  • Tip: Set rate to 0.0 if the operator should pay nothing by default (only specific locations/ranks get per diem).


6. Day Counting

day_counting_mode

  • Type: String

  • Default: 'full'

  • What it does: Controls how away days are converted into payable per diem units.

Mode

Behavior

Use when...

'full'

Each calendar day the crew is away counts as 1 full day.

You pay a flat daily rate regardless of hours.

'incremental'

Days are counted as fractions based on hours away that calendar day. The thresholds in incremental_thresholds determine the fraction.

You pay different amounts based on how many hours the crew is away (e.g., 25% for 1-5h, 50% for 6-11h, 100% for 12h+).

'half_day_window'

A departure before the window start hour or an arrival after the window end hour adds an extra half-day.

You use a specific time window (e.g., 12:00-19:00) to determine if an extra half-day is owed on departure/arrival days.

'tiered'

Same hour-based thresholds as 'incremental', but instead of multiplying fraction x rate, each tier uses a fixed amount defined per location in the rate objects.

You use exact amounts per tier (e.g., "Poland: full day = 100 EUR, half day = 62 EUR") rather than calculating fractions of a single rate.

Incremental and Tiered Mode Settings

Flag

Type

Default

Description

incremental_thresholds

Array of objects

[{'min_hours': 12, 'fraction': 1.0, 'tier': 'full'}, {'min_hours': 6, 'fraction': 0.5, 'tier': 'half'}, {'min_hours': 1, 'fraction': 0.25, 'tier': 'quarter'}]

Hour brackets for incremental and tiered modes. Evaluated top-down — the first threshold where the crew's hours away that day meet or exceed min_hours wins. In incremental mode, the fraction value is multiplied by the rate. In tiered mode, the tier key looks up a fixed amount from the rate object's tiers sub-object. Customize the hour breakpoints, fractions, and tier names to match the operator's per diem schedule.

Example interpretation of the default: If crew is away 12+ hours on a day, full tier / fraction 1.0. 6-11 hours, half tier / fraction 0.5. 1-5 hours, quarter tier / fraction 0.25.

Tiered Mode — Rate Configuration

When day_counting_mode is 'tiered', rate objects (airport_rates, country_rates, default_rate, crew_rates, rank_rates) support an optional tiers sub-object that maps tier names to fixed amounts. The tier names must match the tier keys in incremental_thresholds.

Example — country rate with tiers:

'EP': {'rate': 100, 'currency': 'eur', 'tiers': {'full': 100, 'half': 62, 'quarter': 30}}

In this example, a full day in Poland pays exactly 100 EUR, a half day pays 62 EUR, and a quarter day pays 30 EUR - regardless of the fraction values in the thresholds.

Fallback behavior: If a rate object has no tiers sub-object (or the matched tier key is missing), the template falls back to fraction x rate - the same calculation as incremental mode. You can mix tiered and non-tiered rates: locations with tiers use fixed amounts, locations without use fraction-based calculation.

Currency conversion still applies - tier amounts are defined in the rate object's currency and converted to the display currency automatically.

Half-Day Window Settings

Flag

Type

Default

Description

half_day_start_hour

Integer

12

The start of the half-day window (hour of day, 24h format). A departure before this hour on the first day of a trip adds a half-day bonus.

half_day_end_hour

Integer

19

The end of the half-day window (hour of day, 24h format). An arrival after this hour on the last day of a trip adds a half-day bonus.

Final Day Handling

Flag

Type

Default

Description

skip_arrival_day

boolean

true

When true, the last day of a trip (arrival day) is not paid if no new duty starts on it - the crew is considered "returning home." When false, the arrival day always counts. Set to false if you pay per diem for the day the crew returns to base.


7. Qualification and Filtering

These flags control which duties qualify for per diem and what conditions must be met.

SIM Exclusion

Flag

Type

Default

Description

exclude_sim_duties

boolean

true

When true, simulator sessions are excluded from per diem and do not start, extend, or close trips. SIM is detected from the flight list (a sector whose flight_no is SIM) or a simulator activity_type. SIM duties that fall mid-trip still appear in the sector-detail breakdown (marked with a SIM badge) when show_sector_details is on. Set to false if you pay per diem for simulator duties.

Overnight Qualification

Flag

Type

Default

Description

require_overnight_qualification

boolean

false

When true, a duty only qualifies for per diem if it involves a qualified overnight stop (the crew stays away overnight meeting minimum rest requirements). Enable if you only pay per diem when the crew stays overnight at a destination.

overnight_min_rest_hours

Integer

4

Minimum rest hours at the destination to count as a qualified overnight stop. Only applies when require_overnight_qualification is true. Adjust to your minimum rest requirement.

overnight_min_departure_hour

Integer

6

The earliest departure hour (24h format) that qualifies - departures before this hour on the next day may not count as a proper overnight. Only applies when require_overnight_qualification is true.

Duty Type Filtering

Flag

Type

Default

Description

valid_duty_types

Array of strings

['duty', 'training']

Which roster duty types are processed for per diem. Only roster records with a duty_type matching one of these values are considered. Modify to include or exclude duty types (e.g., add 'standby' if standby should generate per diem).

valid_duty_names

Array of strings

[] (empty)

Further filter roster records by specific duty names. When empty, all duties matching valid_duty_types are accepted. When populated, only duties whose name matches one in this list qualify.

unpaid_duty_names_override

Array of strings

[] (empty)

Roster duty names that cancel per diem even during an active away-from-base period. If the crew is away and a duty with one of these names appears, that day is not paid. Use for unpaid leave or certain standby types the operator excludes.

Travel Requirement

Flag

Type

Default

Description

check_involves_travel

boolean

true

When true, a duty only qualifies if it involves travel to an airport outside the crew's home base. Ground duties at home base are excluded. When false, all matching duties qualify regardless of location. Set to false if you pay per diem for ground duties at any location.


8. Report Boundary Handling

These flags handle edge cases at the start and end of the reporting period, when the crew's actual trip may extend beyond the report dates.

Flag

Type

Default

Description

assume_away_if_first_duty_outside

boolean

true

When true, if the first duty in the report period starts at an airport that is not the crew's home base, the template assumes the crew has been away since the start of the report period. Set to false if per diem should only count from duties visible within the report period.

assume_away_if_last_duty_outside

boolean

true

When true, if the last duty in the report period ends at an airport that is not the crew's home base, the template assumes the crew remains away through the end of the report period. Set to false if per diem should stop at the last duty visible in the report.

Typical usage: Leave both true for accurate per diem across month boundaries (a crew member on a multi-day trip spanning two months is correctly counted in both). Set both to false only if the operator strictly limits per diem to duties fully within the report window.


9. Currency

Display Currency

Enable exactly one of these flags to set which currency the report displays:

Flag

Type

Default

Description

show_in_USD

boolean

false

Display all amounts in US Dollars.

show_in_GBP

boolean

false

Display all amounts in British Pounds.

show_in_EUR

boolean

true

Display all amounts in Euros.

show_in_SEK

boolean

false

Display all amounts in Swedish Kronor.

Important: Set only one to true. The template converts all rates to the selected display currency using the exchange rates below. (Default display currency is EUR.)

Exchange Rates

Flag

Type

Default

Description

exchange_rates

Hash (object)

{'eur': 1.0, 'usd': 1.18, 'gbp': 0.86, 'sek': 11.50}

Conversion rates with EUR as the base (EUR is always 1.0). To update: set each currency's value to how many units of that currency equal 1 EUR. Update regularly for accurate cross-currency conversion.

Currency Symbols

Flag

Type

Default

Description

currency_symbols

Hash (object)

{'usd': '$', 'gbp': '£', 'eur': '€', 'sek': 'kr'}

Display symbols shown next to amounts. Only change if the operator prefers alternate formatting (e.g., 'USD' instead of '$').

If you want another currency to be added to the report, let us know through Customer Portal ticket.


10. Presentation

Flag

Type

Default

Description

show_sector_details

boolean

false

When true, shows a day-by-day sector breakdown under each away period — each day lists flights, positionings, roster activities, the airports, and the rate applied. Enable if you want full transparency on how each day's per diem was calculated.

show_days_outside_home_base

boolean

false

When true, adds a "Days Outside Home Base" column to the summary table. This is a count of processed days and may not align with very permissive settings. Set to true only if you want to see number of processed days.

show_per_diem_columns

boolean

true

When true, shows the per diem monetary value columns (rate, total). When false, hides monetary columns — useful for reports that only need duty/trip data without financial figures.

show_rank_column

boolean

false

When true, adds a column showing the crew member's rank/position. Enable when rank-based rates are in use / when you want ranks visible.


11. Airport Discontinuity Warnings

The template automatically flags airport discontinuities — a duty that starts away from home base with no preceding trip that brought the crew there. These appear as:

  • DISC badge — an orange label next to the affected crew member in summary,

    image-20260703-154709.png
  • Orange-highlighted rows — the affected period row is visually highlighted, with a note

    image-20260703-154739.png

When they appear: Typically when a positioning leg that would have carried the crew to that airport is missing. The most common cause is ground-transport positioning that is not linked to its duty, so the route appears to start away from base. Other causes: a missing or unrecorded positioning, ground travel between airports, or a data entry error in Leon.

No configuration needed — discontinuity warnings are always active. They are informational only and do not affect per diem totals. Use them to spot data-quality issues and to confirm Crew Positionings linkage is healthy.


Quick Reference — All Flags

#

Flag

Type

Default

1

calculate_away_from_base_periods

boolean

true

2

calculate_only_FDP_duties

boolean

false

3

include_positioning_flights

boolean

true

4

calculate_domestic_per_diem

boolean

true

5

trip_qualification_mode

string

'default'

6

use_crew_bt

boolean

true

7

use_iata_codes

boolean

true

8

use_crew_rates

boolean

false

9

use_rank_rates

boolean

false

10

use_two_pass_rates

boolean

true

11

day_counting_mode

string

'full'

12

incremental_thresholds

array

[{12: 1.0}, {6: 0.5}, {1: 0.25}]

13

half_day_start_hour

integer

12

14

half_day_end_hour

integer

19

15

skip_arrival_day

boolean

true

16

exclude_sim_duties

boolean

true

17

require_overnight_qualification

boolean

false

18

overnight_min_rest_hours

integer

4

19

overnight_min_departure_hour

integer

6

20

valid_duty_types

array

['duty', 'training']

21

valid_duty_names

array

[]

22

unpaid_duty_names_override

array

[]

23

check_involves_travel

boolean

true

24

assume_away_if_first_duty_outside

boolean

true

25

assume_away_if_last_duty_outside

boolean

true

26

show_in_USD

boolean

false

27

show_in_GBP

boolean

false

28

show_in_EUR

boolean

true

29

show_in_SEK

boolean

false

30

exchange_rates

hash

{'eur':1.0, 'usd':1.18, 'gbp':0.86, 'sek':11.50}

31

currency_symbols

hash

{'usd':'$', 'gbp':'£', 'eur':'€', 'sek':'kr'}

32

show_sector_details

boolean

false

33

show_days_outside_home_base

boolean

false

34

show_per_diem_columns

boolean

true

35

show_rank_column

boolean

false

36

crew_rates

hash

{}

37

rank_rates

hash

{}

38

crew_positions

hash

{}

39

airport_rates

hash

{}

40

country_rates

hash

{}

41

default_rate

object

{'rate':10.0, 'currency':'eur'}