# Pilot's Toolkit — Documentation

*RC1 — February 28, 2026*

---

## Overview

Pilot's Toolkit is a collection of aviation utilities for flight crews. It runs as a web app (React/Vite) and an Android app (Kotlin/Jetpack Compose). Both platforms share the same data files and feature set.

All calculations carry the disclaimer: **Not for operational use — verify all calculations independently.**

---

## Modules

### Fuel Order Calculator

Enter total fuel required, fuel on board, and fuel density to get the order quantity in US gallons, imperial gallons, and liters, all rounded up to the nearest 10 units.

Fuel density is entered at the 15°C reference temperature. The three-way toggle switches between specific gravity, kg/L, and lb/US gallon — values auto-convert when you switch units. If you don't have a fuel ticket, the default (0.810 SG / 6.76 lb/gal) is typical Jet-A.

The ambient temperature field corrects density using the ASTM D1250 Table 6B thermal expansion model. Temperature defaults to °C; tap the toggle to switch to °F (the value converts live). The primary volume unit (US gal, imperial gal, or liters) is set in the hamburger menu and defaults based on locale.

### Pavement Strength

Calculates ACN/ACR values for the selected aircraft at a given weight, using linear regression fits from official aircraft manual charts. Supports PCN (legacy) and PCR (ICAO 2024+ transition), rigid and flexible pavement types, and four subgrade categories (A through D).

Enter the airport's published PCN or PCR value in the comparison field to see a pass/exceed result with percentage. When the aircraft's rating exceeds the pavement rating, the calculator reverse-solves the formula to determine the maximum allowable weight for that pavement, rounded down to the nearest 500 lbs. If the resulting weight is below the aircraft's basic empty weight, the message indicates the pavement cannot support the aircraft at any operational weight.

Some aircraft (G450, G650) show a computed ESWL using a weight-based formula. Others (G700) show fixed ESWL thresholds (single wheel and dual wheel) because the manufacturer publishes specific values rather than a formula.

### Crosswind Calculator

Enter wind as a five-digit string — for example, typing `31015` auto-formats to `310/15` when the field loses focus. Gust goes in the separate field. Runway accepts either a runway number (01–36) or a heading (010–360).

The output shows headwind/tailwind and crosswind components for both sustained and gust winds. Tailwind values display in red.

### Fuel Buckets

Drag the slider to a stage length and see the interpolated fuel burn rate. Data comes from either the aircraft's built-in bucket schedule (G450 only at RC1) or an operator-specific profile loaded via CSV or JSON import in the hamburger menu.

**CSV format** for import: two columns — stage length (hours, decimal) and fuel burn (lbs/hr). A header row is optional and auto-detected.

```
stage_length,fuel_burn
0.1,650
1.0,548.5
3.0,449
```

Profiles are named and stored per aircraft. See the "Fuel Profile JSON Format" section below for the JSON import/export schema.

### HF Frequencies

Fetches current ARINC HF frequency schedules for Atlantic and Pacific. Shows a staleness indicator — frequencies change on propagation-based windows (typically every few hours), so the 4-hour threshold flags when you should reload. The link opens the ARINC page directly in your browser.

### Passdown

A crew changeover form for sharing operational information: crew emails, registration, date, airport, FBO, FBO phone, maintenance status, and narrative. Records are stored locally (IndexedDB on web, SharedPreferences on Android).

The history view shows all passdowns with age-based color coding: today's entries are highlighted, yesterday's are muted, older entries are dimmed. Filter by registration and/or date range. Batch export produces a `.ptz` file of all matching results. See ".ptz File Format" below for the schema.

### Flight Level ↔ Meters

Two modes. **Meters ↔ Feet Lookup** searches the OEM conversion table (ICAO Table 15, 107 entries). Type in either direction and results filter live. **China FLAS** shows the China RVSM Flight Level Allocation Scheme with dual toggle buttons for eastbound (0°–179°) and westbound (186°–359°) filtering. RVSM levels are color-coded blue (east) and amber (west). When both buttons are active, all levels display.

ATC clears flight levels in meters; the pilot sets the altimeter to the corresponding feet value from the table. Due to rounding, the onboard metric readout may differ from the cleared value by up to 30 meters.

**Do not use for approach minima.** Chart values are rounded to the nearest 100 feet.

### Crew Rest (Augmented Operations)

Basic rest period calculator for augmented crews of 3 or 4 pilots. Enter departure time (Zulu) and ETE, then enter crew member IDs or names in the crew fields (minimum 3, 4th is optional).

**ETE input** accepts three formats: `10:30` (hours:minutes with colon), `1030` (HHMM — auto-formats to `10:30` on blur), or `630` (total minutes, 1–3 digits without colon). The leading zero disambiguates: `0630` is HHMM (6h 30m), `630` is 630 minutes.

**Calculation:** All crew on deck for the first 30 minutes after departure and the last 30 minutes before arrival. The remaining flight time is divided equally among crew members in sequential rotation. Any remainder minutes from rounding are absorbed into the final all-hands block (crew returns to deck slightly early — conservative).

**Output:** Flight summary showing departure, ETE, arrival, all-hands windows, total rest window with total minutes, and per-crew rest duration. Rest schedule shows each crew member's rest begin/end times in Zulu.

**Lock toggle:** Tap the lock button to prevent accidental edits while referencing the schedule mid-flight. When locked, all fields are read-only and the Clear button is hidden. On Android, state persists across tab switches.

This is a basic equal-division implementation. A more sophisticated version incorporating 14 CFR Part 117 rest facility classes, acclimated time zones, and regulatory compliance is under consideration for future development but may not be attainable due to the complexity and liability of making legal/illegal determinations.

---

## Data File Structure

All aircraft-specific and reference data lives in JSON files shared by both platforms. This is the section to read if you need to edit existing data or add a new airframe.

### Directory Layout

**Web** (Vite `src/` directory):
```
src/
  data/
    reference.json
    aircraft/
      G450.json
      G500.json
      G650.json
      G700.json
```

**Android** (`app/src/main/`):
```
assets/
  reference.json
  aircraft/
    G450.json
    G500.json
    G650.json
    G700.json
```

Both platforms read the same JSON — just placed in the appropriate directory. The web app imports via Vite at build time; Android reads from `assets/` at runtime via `DataLoader.init(context)`.

### Error Handling

Each aircraft file loads independently. If `G700.json` has a syntax error, the other three airframes still load normally. The app logs a warning rather than crashing. On Android, `DataLoader.loadErrors` contains a map of failed aircraft IDs and error messages.

### reference.json

Airframe-agnostic data. Not included in per-aircraft export/import.

```json
{
  "_meta": {
    "format": "pilot-toolkit-data",
    "version": 3
  },
  "pavementSubgrades": {
    "A": "A – High",
    "B": "B – Medium",
    "C": "C – Low",
    "D": "D – Ultra Low"
  },
  "metersToFeet": {
    "source": "...",
    "caution": "Do not use for approach minima.",
    "entries": [
      [15100, 49500],
      [300, 1000]
    ]
  },
  "chinaFLAS": {
    "source": "...",
    "entries": [
      [15500, 50900],
      [100, 300]
    ],
    "rvsmEastbound": [29100, 31100, 33100, 35100, 37100, 39100, 41100],
    "rvsmWestbound": [30100, 32100, 34100, 36100, 38100, 40100]
  }
}
```

The `entries` arrays are `[meters, feet]` pairs, sorted descending by meters. RVSM arrays list feet values assigned to each direction.

### Aircraft JSON Files

Each file represents one airframe. The filename (minus `.json`) is the aircraft ID used throughout the app.

#### Required Fields

| Field | Type | Description |
|-------|------|-------------|
| `_meta.format` | string | Must be `"pilot-toolkit-aircraft"` |
| `_meta.version` | number | Schema version (currently `3`) |
| `_meta.aircraftId` | string | Must match the filename |
| `label` | string | Display name (e.g., `"G700"`) |
| `available` | boolean | `true` if the aircraft has enough data for calculations; `false` shows it as a placeholder |

#### Optional Fields (null if not available)

| Field | Type | Description |
|-------|------|-------------|
| `weightLimits` | object | `minBew`, `mtow`, `maxRamp` (all integers, lbs) |
| `eswl` | object | ESWL configuration — see below |
| `pcn` | object | PCN regression coefficients — see below |
| `pcr` | object | PCR regression coefficients — same structure as `pcn` |
| `pcnError` | string | Error margin (e.g., `"±2%"`) |
| `pcrError` | string | Error margin |
| `windLimitations` | object | Reserved for future use, currently `null` |
| `fuelBuckets` | array | Array of `[stageLength, fuelBurn]` pairs |

#### ESWL — Two Types

The `eswl` field uses a `type` discriminator.

**Formula type** (G450, G650): ESWL is calculated as `weight × weightFactor ÷ wheelFactor`.

```json
"eswl": {
  "type": "formula",
  "weightFactor": 0.45,
  "wheelFactor": 1.23
}
```

**Thresholds type** (G700): Manufacturer publishes fixed values.

```json
"eswl": {
  "type": "thresholds",
  "singleWheel": { "label": "S-85", "value": 85000, "unit": "lbs" },
  "dualWheel": { "label": "D-108", "value": 108000, "unit": "lbs" },
  "note": "Unrestricted operations at or above these values."
}
```

#### Pavement Coefficients (PCN/PCR)

Linear regression: `result = slope × weight + intercept`. The structure is the same for both `pcn` and `pcr`:

```json
"pcn": {
  "rigid": {
    "A": { "slope": 0.0003323, "intercept": -2.4247 },
    "B": { "slope": 0.0003268, "intercept": -1.2011 },
    "C": { "slope": 0.0003521, "intercept": -2.1385 },
    "D": { "slope": 0.0003547, "intercept": -1.5244 }
  },
  "flexible": {
    "A": { "slope": 0.000286, "intercept": -2.4517 },
    "...": "..."
  }
}
```

Each combination of pavement type (rigid/flexible) and subgrade (A/B/C/D) has its own slope and intercept. These are derived from linear regression fits to official aircraft manual charts.

#### Fuel Buckets

Array of `[stageLength, fuelBurn]` pairs sorted by stage length. Stage length is in decimal hours; fuel burn is in lbs/hr.

```json
"fuelBuckets": [
  [0.1, 650.0],
  [1.0, 548.5],
  [3.0, 449.0],
  [9.9, 456.6]
]
```

Set to `null` if no built-in data is available for the airframe. Users can still load operator-specific data via CSV or JSON import.

### Adding a New Airframe

1. Create a new JSON file in `aircraft/` (e.g., `G280.json`).
2. Set `_meta.aircraftId` and `label` to match.
3. Set `available` to `false` if the data is incomplete — the app shows the aircraft in the selector but disables calculations.
4. Populate whichever fields you have. Everything except `_meta`, `label`, and `available` is nullable.
5. On Android, the file is auto-discovered from the `aircraft/` directory. On web, add an import line and a `loadAircraft()` call in `toolkit.jsx`.

---

## Exchange Formats

### .ptz File Format

The `.ptz` format is a **gzipped JSON array** of passdown objects. The format is open — third-party tools and operators are free to generate or consume `.ptz` files.

To inspect a `.ptz` file manually: `gunzip < PASSDOWN_N12345_2026-02-28.ptz | python3 -m json.tool`

Each object in the array has the following fields:

| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Unique identifier (UUID-style) |
| `date` | string | Passdown date (`YYYY-MM-DD`) |
| `registration` | string | Aircraft registration (e.g., `N12345`) |
| `airport` | string | ICAO code (e.g., `KPTK`) |
| `fbo` | string | FBO name |
| `fboPhone` | string | FBO phone number |
| `pic` | string | PIC email |
| `sic` | string | SIC email |
| `ca1` | string | Lead cabin attendant email |
| `oncomingPic` | string | Oncoming PIC email |
| `oncomingSic` | string | Oncoming SIC email |
| `maintenance` | string | Maintenance status / squawks |
| `narrative` | string | Free-text operational notes |
| `createdAt` | string | ISO 8601 timestamp |
| `updatedAt` | string | ISO 8601 timestamp |
| `lastSentAt` | string | ISO 8601 timestamp (empty string if never sent) |

Filename conventions:
- Single passdown: `PASSDOWN_REG_DATE.ptz`
- Batch (single tail): `PASSDOWN_BATCH_REG_FROMDATE_TODATE.ptz`
- Batch (mixed tails): `PASSDOWN_BATCH_Nrecords_FROMDATE_TODATE.ptz`

On import, duplicates are detected by `id` and flagged in the preview screen.

### Fuel Profile JSON Format

Exported as `FUEL_PROFILE_name_aircraft.json`. The app validates the `format` field on import.

```json
{
  "format": "pilot-toolkit-fuel-profile",
  "version": 1,
  "aircraft": "G450",
  "aircraftLabel": "G450",
  "name": "FLEXJET G450",
  "entries": 99,
  "buckets": [
    [0.1, 650.0],
    [1.0, 548.5],
    [9.9, 456.6]
  ]
}
```

| Field | Required on Import | Description |
|-------|-------------------|-------------|
| `format` | Yes | Must be `"pilot-toolkit-fuel-profile"` |
| `aircraft` | Yes | Target aircraft ID (e.g., `"G450"`) |
| `name` | No | Profile name (defaults to `"Imported"`) |
| `buckets` | Yes | Array of `[stageLength, fuelBurn]` pairs |
| `version`, `aircraftLabel`, `entries` | No | Informational; ignored on import |

### Fuel Bucket CSV Format

Two columns: stage length (decimal hours) and fuel burn (lbs/hr). Header row is optional and auto-detected.

```
stage_length,fuel_burn
0.1,650
1.0,548.5
3.0,449
9.9,456.6
```

On import, the app prompts for a profile name. The data replaces any existing profile for the selected aircraft.

---

## Settings

Available in the hamburger menu (☰):

- **Aircraft selector** — Sets the active airframe for pavement, fuel order, and fuel bucket modules. "Set Default" persists the choice across sessions.
- **Dark mode** — Toggles light/dark theme.
- **Primary volume unit** — US gallons, imperial gallons, or liters. Affects fuel order output. Auto-detected from locale on first use.
- **Passdown retention** — How long to keep passdown history: 3, 6, 12, 24, or 36 months, or never delete. Auto-purge runs on app open.
- **Fuel profiles** — View, export, and clear loaded profiles per aircraft. CSV and JSON import.
