# Блочное время

Интеграция ML-прогнозов времени полёта: оценки от гейта до гейта с учётом типа ВС и доверительными границами.



Готовые к запуску примеры оценки блочного времени (от гейта до гейта) с помощью ML-модели SkyLink. Подробности о модели, метриках точности и поддерживаемых типах ВС см. на странице [ML-оценка времени полёта](/docs/v31/flight-time).

| # | Вопрос                                       | Используемые данные |
| - | -------------------------------------------- | ------------------- |
| 1 | Оценить блочное время KJFK → KLAX на B738    | GET /ml/flight-time |
| 2 | Сравнить время полёта по 4 маршрутам из EGLL | GET /ml/flight-time |

<DocRequirements>
  <DocRequirementAuth />
</DocRequirements>

### Установка [#install]

Приведённые ниже скрипты используют `requests` для вызовов API и `rich` для вывода в терминал. Установите один раз:

```bash
pip install requests rich
```

***

### 1. Оценка блочного времени: KJFK → KLAX на B738 [#1-block-time-estimate-kjfk--klax-on-b738]

**Вопрос:** каково расчётное блочное время (от гейта до гейта) для 737-800 на маршруте KJFK–KLAX?

Модель возвращает расчётное время и диапазон min/max, полученный на основе исторических данных (GradientBoosting, R²≈0.98). Ветер не моделируется — фактическое время будет отличаться в зависимости от маршрутизации и условий.

```python
import os

import requests
from rich.console import Console
from rich.panel import Panel

console = Console()

HEADERS = {
    "X-RapidAPI-Key": os.getenv("RAPIDAPI_KEY", "YOUR_RAPIDAPI_KEY"),
    "X-RapidAPI-Host": "skylink-api.p.rapidapi.com",
}
BASE = "https://skylink-api.p.rapidapi.com"


def fetch_flight_time(
    origin: str, destination: str, aircraft: str | None = None
) -> dict | None:
    params: dict[str, str] = {"from": origin, "to": destination}
    if aircraft:
        params["aircraft"] = aircraft
    r = requests.get(
        f"{BASE}/ml/flight-time",
        headers=HEADERS,
        params=params,
        timeout=(10, 15),
    )
    if r.status_code == 404:
        return None
    r.raise_for_status()
    return r.json()


def fmt_mins(minutes: int | None) -> str:
    if minutes is None:
        return "—"
    return f"{minutes // 60}h {minutes % 60:02d}m"


def main() -> None:
    data = fetch_flight_time("KJFK", "KLAX", aircraft="B738")
    if not data:
        console.print("[red]No estimate returned.[/red]")
        return

    origin = data.get("origin", "KJFK")
    dest = data.get("destination", "KLAX")
    aircraft = data.get("aircraft_type", "B738")
    est_display = data.get("estimated_hours_display") or fmt_mins(
        data.get("estimated_minutes")
    )
    min_m = data.get("min_minutes")
    max_m = data.get("max_minutes")
    distance = data.get("distance_nm")

    range_str = f"{fmt_mins(min_m)} – {fmt_mins(max_m)}"

    body = (
        f"Route:      [bold]{origin} → {dest}[/bold]\n"
        f"Aircraft:   {aircraft}\n"
        f"Estimated:  [bold green]{est_display}[/bold green]\n"
        f"Range:      {range_str}\n"
        f"Distance:   {distance:,.1f} nm" if distance else ""
    )

    console.print(Panel(body, title="Block Time Estimate", style="blue"))
    console.print(
        "[dim]Estimate is statistical average for this route/type — "
        "actual time varies with winds and routing.[/dim]"
    )


if __name__ == "__main__":
    main()
```

**Результат:** панель с маршрутом, типом ВС, расчётным блочным временем (в формате для отображения), диапазоном min–max и [расстоянием по ортодромии](https://en.wikipedia.org/wiki/Great-circle_distance) в морских милях.

***

### 2. Сравнение маршрутов из EGLL [#2-route-comparison-from-egll]

**Вопрос:** как соотносится время полёта из лондонского Хитроу по четырём дальнемагистральным направлениям?

Запросите все четыре маршрута параллельно и отсортируйте по расчётному времени по возрастанию. Полезно для специалистов по расписаниям, выявляющих короткие окна для стыковок.

```python
import os
import concurrent.futures

import requests
from rich.console import Console
from rich.table import Table

console = Console()

HEADERS = {
    "X-RapidAPI-Key": os.getenv("RAPIDAPI_KEY", "YOUR_RAPIDAPI_KEY"),
    "X-RapidAPI-Host": "skylink-api.p.rapidapi.com",
}
BASE = "https://skylink-api.p.rapidapi.com"

ROUTES = [
    ("EGLL", "KJFK"),
    ("EGLL", "OMDB"),
    ("EGLL", "YSSY"),
    ("EGLL", "RJTT"),
]


def fetch_flight_time(origin: str, destination: str) -> tuple[str, str, dict | None]:
    try:
        r = requests.get(
            f"{BASE}/ml/flight-time",
            headers=HEADERS,
            params={"from": origin, "to": destination},
            timeout=(10, 15),
        )
        if r.status_code == 404:
            return origin, destination, None
        r.raise_for_status()
        return origin, destination, r.json()
    except Exception as exc:  # noqa: BLE001
        console.print(f"[red]{origin}→{destination}: {exc}[/red]")
        return origin, destination, None


def fmt_mins(minutes: int | None) -> str:
    if minutes is None:
        return "—"
    return f"{minutes // 60}h {minutes % 60:02d}m"


def main() -> None:
    with concurrent.futures.ThreadPoolExecutor(max_workers=4) as pool:
        results = list(pool.map(lambda r: fetch_flight_time(*r), ROUTES))

    valid = [
        (orig, dest, d)
        for orig, dest, d in results
        if d and d.get("estimated_minutes") is not None
    ]
    valid.sort(key=lambda x: x[2].get("estimated_minutes", 0))

    table = Table(
        title="EGLL Long-haul Route Comparison (model default aircraft)",
        show_lines=True,
    )
    table.add_column("Route", style="cyan", no_wrap=True)
    table.add_column("Distance (nm)", justify="right")
    table.add_column("Est. Time", justify="center", style="bold")
    table.add_column("Min", justify="center")
    table.add_column("Max", justify="center")

    for orig, dest, d in valid:
        distance = d.get("distance_nm")
        dist_str = f"{distance:,.0f}" if distance else "—"
        table.add_row(
            f"{orig} → {dest}",
            dist_str,
            fmt_mins(d.get("estimated_minutes")),
            fmt_mins(d.get("min_minutes")),
            fmt_mins(d.get("max_minutes")),
        )

    console.print(table)
    console.print(
        "[dim]Statistical estimates — wind not modelled. "
        "Routes sorted by estimated time ascending.[/dim]"
    )


if __name__ == "__main__":
    main()
```

**Результат:** таблица, отсортированная от самого короткого маршрута к самому длинному, с маршрутом, расстоянием в морских милях, расчётным временем и историческим диапазоном min/max для каждой пары городов.
