Calculation Progress

Track the progress of long-running performance calculations (takeoff/landing) in real-time to provide user feedback.

Overview

Performance calculations can take several seconds to complete, especially for complex scenarios involving multiple derate options, optimum configurations, or parallel autobrake calculations. The Progress API allows you to poll for calculation status updates while a calculation is in progress.

How It Works

When you submit a takeoff or landing performance request, the calculation runs on the server while you poll the progress endpoint in parallel. Each poll returns the latest status, a human-readable description, the current progress / total values for bar rendering, and an elapsed_ms timer so you can surface timing information to your users or detect stalled requests.

Get Progress

POST /api

Retrieves the current progress status of a running performance calculation. Progress is tracked per API key, so each user's calculation progress is isolated.

Request Parameters

Parameter Type Required Description
query string Required Must be "progress"

Request Body

JSON
{
  "query": "progress"
}

cURL Example

cURL
curl -X POST "https://api.virtualperformancetool.com/api" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-API-Version: 1.3.6" \
  -d '{
    "query": "progress"
  }'

Response (Starting)

Immediately after a calculation is submitted, before the first step executes, the server writes an initial state you can use to show an indeterminate spinner or a 0% bar.

JSON
{
  "datetime": "2026-03-19 14:30:44",
  "title": "Starting",
  "description": "Preparing calculation...",
  "progress": 0,
  "total": 100,
  "status": "running",
  "completed": false,
  "elapsed_ms": 38
}

Response (In Progress)

JSON
{
  "datetime": "2026-03-19 14:30:45",
  "title": "Takeoff Request Submission",
  "description": "Calculating the regulated TOW based on TOGA...",
  "progress": 20,
  "total": 30,
  "status": "running",
  "completed": false,
  "elapsed_ms": 1240
}

Response (Completed)

JSON
{
  "datetime": "2026-03-19 14:30:52",
  "title": "Preparing result",
  "description": "Completed",
  "progress": 30,
  "total": 30,
  "status": "completed",
  "completed": true,
  "elapsed_ms": 3870
}

Response (Error)

When a calculation fails, status is set to "error" and an error_message field is included. Stop polling and surface the message to the user.

JSON
{
  "datetime": "2026-03-19 14:30:46",
  "title": "Takeoff Request Submission",
  "description": "Calculation failed",
  "progress": 20,
  "total": 30,
  "status": "error",
  "completed": true,
  "error_message": "No valid rating available for windshear calculation",
  "elapsed_ms": 1580
}

Response (No Active Calculation)

If no calculation has been started yet for the authenticated user, the endpoint returns null.

JSON
null

Polling Strategy

To track calculation progress effectively, implement a polling loop that queries the progress endpoint at regular intervals.

Recommended Polling Interval

Poll every 500ms to 1000ms for a smooth progress bar experience. Avoid polling more frequently than 200ms as it creates unnecessary load without improving user experience.

When to Stop Polling

Stop polling as soon as status is no longer "running" — both "completed" and "error" are terminal states. The main calculation HTTP request will also return at that point, so you can safely use either signal to stop the loop.

  • status === "completed" — success, the main calculation response is ready
  • status === "error" — failure, read error_message and do not wait for the main request

Implementation Tips

  • Start polling immediately after submitting the performance calculation request
  • Use status as your primary check — completed: boolean is kept for backward compatibility but status is more expressive
  • Handle null responses gracefully (no calculation in progress yet) by waiting briefly before the first retry
  • Calculate percentage as (progress / total) * 100. Note that total can grow slightly mid-calculation when optional code paths execute — this is intentional so the bar never jumps backwards
  • Use description for the user-facing status message and title for the current phase label
  • Use elapsed_ms to detect stalled calculations and trigger a timeout after a reasonable threshold (e.g. 60 seconds)

Response Fields

Field Type Description
datetime string UTC timestamp of the last progress update (YYYY-MM-DD HH:MM:SS)
title string Current calculation phase label — suitable for a section header (e.g. "Takeoff Request Submission")
description string Detailed status message for the current step — suitable for a sub-label beneath the progress bar (e.g. "Calculating landing distances... (2/4)")
progress number Current progress value. Use with total to compute percentage: progress / total * 100
total number Total progress value. May increase slightly mid-calculation when optional steps execute, ensuring the bar never jumps backwards
status string Terminal state indicator. One of "running", "completed", or "error". Stop polling when this is not "running"
completed boolean true when calculation has ended (success or failure). Kept for backward compatibility — prefer status
elapsed_ms number Milliseconds elapsed since the calculation was submitted. Useful for displaying a timer to the user or detecting stalled requests
error_message string Only present when status is "error". Human-readable reason the calculation failed

Status Values

Value Meaning Action
"running" Calculation is actively executing Continue polling, update the progress bar
"completed" Calculation finished successfully Stop polling, read the result from the main calculation response
"error" Calculation failed Stop polling, display error_message to the user

Calculation Phases

The title field cycles through these values as the calculation progresses:

Phase Title Calculation Type Description
Starting All Brief initial state written before the first step executes
Takeoff Request Submission Takeoff Processing takeoff performance — RTOW, derate selection, V-speeds, windshear
Landing Dispatch Request Submission Landing Dispatch Calculating max landing weight, MACG, icing conditions, most favorable runway
Landing Enroute Request Submission Landing Inflight Calculating landing distances per autobrake setting, weight limitations, Vref additive
Preparing result All Finalizing calculation, assembling response data, generating graphic results
Dynamic descriptions

The description field is more granular than title. For example, during parallel autobrake calculations it updates per response: "Calculating landing distances... (1/4)", "... (2/4)", and so on. The all-intersections takeoff (ALL) fires the full-length runway and every intersection together in one parallel batch, showing "Computing full-length runway + N intersections in parallel...". Show it as the live sub-label under the bar for the best user experience.

Error State

When a calculation throws an unrecoverable error (invalid data, no suitable rating available, server fault, etc.) the progress file is immediately updated with status: "error" before the main calculation request returns. This means your polling loop will detect the failure as soon as it happens — you don't need to wait for the main request to time out or return an error response.

Do Not Suppress Errors in the Main Request

The progress error_message mirrors the Error[].Message from the main calculation response. Use whichever arrives first — both carry the same message.

Example Workflow

A complete example tracking takeoff performance progress, handling the error state, enforcing a timeout via elapsed_ms, and applying a smooth animated progress bar using a requestAnimationFrame lerp loop.

Smooth Animation Pattern

Polling updates a targetPct every 500 ms, but an independent animation loop running on every frame eases displayedPct toward that target. The bar never overshoots the server-confirmed value — it can only catch up to it. This gives a fluid feel regardless of network jitter or variable step sizes.

JavaScript
const POLL_INTERVAL_MS  = 500;
const STALL_TIMEOUT_MS  = 60_000;
const LERP_SPEED        = 0.08; // fraction of remaining gap closed per frame (~60 fps)

// ── Shared animation state ────────────────────────────────────────────────
let targetPct    = 0;   // set by polling
let displayedPct = 0;   // driven by rAF loop
let animating    = false;

function startAnimationLoop() {
  if (animating) return;
  animating = true;

  function tick() {
    if (!animating) return;

    const gap = targetPct - displayedPct;
    if (Math.abs(gap) > 0.1) {
      displayedPct += gap * LERP_SPEED;
    } else {
      displayedPct = targetPct; // snap when close enough
    }

    renderProgressBar(displayedPct);
    requestAnimationFrame(tick);
  }

  requestAnimationFrame(tick);
}

function stopAnimationLoop(finalPct) {
  animating    = false;
  displayedPct = finalPct;
  targetPct    = finalPct;
  renderProgressBar(finalPct);
}

// ── Main entry ────────────────────────────────────────────────────────────
async function calculateTakeoffWithProgress(requestData, token) {
  // Submit the calculation (runs server-side; response arrives when done)
  const calculationPromise = fetch('/api', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer ' + token,
      'Content-Type': 'application/json',
      'X-API-Version': '1.3.6'
    },
    body: JSON.stringify({ query: 'takeoff', data: requestData })
  });

  startAnimationLoop();           // begin smooth bar before first poll arrives
  await pollProgress(token);      // update targetPct as responses arrive

  const response = await calculationPromise;
  return response.json();
}

// ── Polling loop ──────────────────────────────────────────────────────────
async function pollProgress(token) {
  while (true) {
    await sleep(POLL_INTERVAL_MS);

    const res = await fetch('/api', {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer ' + token,
        'Content-Type': 'application/json',
        'X-API-Version': '1.3.6'
      },
      body: JSON.stringify({ query: 'progress' })
    });

    const progress = await res.json();

    if (progress === null) {
      // State file not written yet — calculation hasn't started, retry
      continue;
    }

    // ── Update shared target (animation loop reads this) ──────────────────
    targetPct = Math.min(100, Math.round(progress.progress / progress.total * 100));

    updateStatusTitle(progress.title);
    updateStatusMessage(progress.description);
    updateElapsedTime(progress.elapsed_ms);

    // ── Terminal states ───────────────────────────────────────────────────
    if (progress.status === 'completed') {
      stopAnimationLoop(100);
      break;
    }

    if (progress.status === 'error') {
      stopAnimationLoop(displayedPct);
      showError(progress.error_message);
      break;
    }

    // Stall guard — calculation taking unreasonably long
    if ((progress.elapsed_ms ?? 0) > STALL_TIMEOUT_MS) {
      stopAnimationLoop(displayedPct);
      showError('The calculation is taking too long. Please try again.');
      break;
    }
  }
}

// ── UI helpers (implement to match your framework) ────────────────────────
function renderProgressBar(pct) {
  document.getElementById('progress-bar').style.width = pct.toFixed(1) + '%';
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
CSS alternative for simple cases

If you only need basic smoothing without predictive easing, add transition: width 400ms ease-out to your progress bar element in CSS and call el.style.width = pct + '%' directly from the polling loop. The JavaScript lerp approach above gives finer control — useful when poll intervals are variable or you want to tune the easing curve.

Parallel Requests

The progress polling runs concurrently with the main calculation request. The main request returns the full result when complete, while the progress endpoint provides real-time feedback during processing. You can stop polling as soon as either signal arrives.