---
title: Preflight
source: https://infrared.city/docs/sdk/1.0/python/preflight/
---

# Preflight

Pre-flight diagnostics for SDK runs.

These helpers run client-side and do not call the API. They give the caller a chance to detect configurations that are likely to produce inaccurate results before incurring the wall-time cost of an actual analysis.

## SEVERITY_LEVELS  `module-attribute`

```python
SEVERITY_LEVELS: tuple[str, ...] = (
"info",
"ok",
"marginal",
"warning",
"critical",
)
```

Severity levels in increasing order of concern.

`info` is reserved for cases where the question does not apply (no positive-elevation hours in the window, or invalid input).

## PreflightWarning

Bases: `Exception`

Raised by an opt-in pre-flight gate when the configuration is unlikely to produce reliable results.

Only raised when a caller explicitly opts in via `preflight="raise"`. The default integration mode is `"log"` (logger warning, no raise).

## SunContextResult

Bases: `TypedDict`

Structured result returned by `estimate_sun_context_loss`.

### severity  `instance-attribute`

```python
severity: str
```

One of `SEVERITY_LEVELS`.

### message  `instance-attribute`

```python
message: str
```

Human-readable explanation suitable for a banner.

### min_sun_elevation_deg  `instance-attribute`

```python
min_sun_elevation_deg: Optional[float]
```

Lowest above-horizon sun elevation reached during the window (degrees). `None` when no positive-elevation hours were sampled.

### min_sun_elevation_month  `instance-attribute`

```python
min_sun_elevation_month: Optional[int]
```

Month of the worst-case hour. `None` for sub-horizon or zero-sample windows.

### min_sun_elevation_day  `instance-attribute`

```python
min_sun_elevation_day: Optional[int]
```

Day of the month of the worst-case hour. `None` for sub-horizon or zero-sample windows.

### min_sun_elevation_hour  `instance-attribute`

```python
min_sun_elevation_hour: Optional[int]
```

Hour of the worst-case hour. `None` for sub-horizon or zero-sample windows.

### pattern  `instance-attribute`

```python
pattern: Optional[str]
```

Human-readable hemisphere/season/time-band label, e.g. `"Northern hemisphere, winter, late morning"`.

### buffer_breakeven_height_m  `instance-attribute`

```python
buffer_breakeven_height_m: float
```

Tallest building whose full shadow fits inside `buffer_m` at the worst-case elevation.

### shadow_at_typical_building_m  `instance-attribute`

```python
shadow_at_typical_building_m: float
```

Shadow length of a `typical_building_height_m` building at the worst-case elevation.

### shadow_at_max_building_m  `instance-attribute`

```python
shadow_at_max_building_m: Optional[float]
```

Shadow length of a `max_building_height_m` building. `None` if that argument was not supplied.

## estimate_sun_context_loss

```python
estimate_sun_context_loss(
*,
lat: float,
lon: float = 0.0,
timezone_offset_h: Optional[float] = None,
start_month: int,
start_day: int,
start_hour: int,
end_month: int,
end_day: int,
end_hour: int,
buffer_m: float = 128.0,
typical_building_height_m: float = 25.0,
max_building_height_m: Optional[float] = None,
) -> SunContextResult
```

Estimate whether a tile geometry-context buffer is sufficient.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `lat` | `float` | Polygon centroid in degrees. Latitude drives sun-angle math; longitude drives the clock-hour to solar-hour correction (`solar_hour = clock_hour + lon/15 - timezone_offset_h`). | *required* |
| `lon` | `float` | Polygon centroid in degrees. Latitude drives sun-angle math; longitude drives the clock-hour to solar-hour correction (`solar_hour = clock_hour + lon/15 - timezone_offset_h`). | *required* |
| `timezone_offset_h` | `Optional[float]` | Offset of the locale's clock from UTC, in hours. `None` (the default) approximates it as `round(lon / 15)`, which matches meridian-aligned time zones and is a fine first-order correction for the locales that don't. | `None` |
| `start_month` | `int` | TimePeriod cascade-filter bounds. The function samples the (month, day, hour) grid that the API filter would itself process. Year-wrap windows (e.g. `start_month=11, end_month=2`) are handled. | *required* |
| `start_day` | `int` | TimePeriod cascade-filter bounds. The function samples the (month, day, hour) grid that the API filter would itself process. Year-wrap windows (e.g. `start_month=11, end_month=2`) are handled. | *required* |
| `start_hour` | `int` | TimePeriod cascade-filter bounds. The function samples the (month, day, hour) grid that the API filter would itself process. Year-wrap windows (e.g. `start_month=11, end_month=2`) are handled. | *required* |
| `end_month` | `int` | TimePeriod cascade-filter bounds. The function samples the (month, day, hour) grid that the API filter would itself process. Year-wrap windows (e.g. `start_month=11, end_month=2`) are handled. | *required* |
| `end_day` | `int` | TimePeriod cascade-filter bounds. The function samples the (month, day, hour) grid that the API filter would itself process. Year-wrap windows (e.g. `start_month=11, end_month=2`) are handled. | *required* |
| `end_hour` | `int` | TimePeriod cascade-filter bounds. The function samples the (month, day, hour) grid that the API filter would itself process. Year-wrap windows (e.g. `start_month=11, end_month=2`) are handled. | *required* |
| `buffer_m` | `float` | Effective per-cell context buffer. Pass `128` for the current `SOLAR_TILING_CONFIG` default, `205` for a wind-style 50% overlap (inner-256 crop), or whatever a custom configuration produces. | `128.0` |
| `typical_building_height_m` | `float` | Reference building height for the headline severity. Default `25` is reasonable for European mid-rise; raise for high-rise contexts (50–100+ m). | `25.0` |
| `max_building_height_m` | `Optional[float]` | Optional worst-case height. When supplied, drives the severity scoring (`severity = breakeven / max(typical, max)`) so that a single tall outlier does not slip past as `ok`. | `None` |

Returns:

| Type | Description |
| --- | --- |
| `[SunContextResult](#infrared_sdk.preflight.SunContextResult)` | TypedDict with `severity` (one of `SEVERITY_LEVELS`), a human-readable `message`, and supporting numerical fields. The function never raises on bad input; it returns `severity="info"` with a diagnostic message instead. |

## required_buffer_m

```python
required_buffer_m(building_height_m: float, sun_elevation_deg: float) -> float
```

Geometric buffer needed for a single building's full shadow to fit.

Returns `+inf` for non-positive sun elevations.

## run_preflight_safely

```python
run_preflight_safely(
payloads,
polygon_centroid_lat: Optional[float] = None,
polygon_centroid_lon: Optional[float] = None,
mode: str = "log",
logger=None,
buffer_m: float = 128.0,
) -> None
```

Defensive wrapper around `estimate_sun_context_loss` for use inside the SDK's area-orchestration entry points.

The whole body is wrapped so that **no preflight failure ever propagates** — internal exceptions emit a debug log and return. Only an explicit `mode="raise"` plus a real warning/critical verdict will raise `PreflightWarning`.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `payloads` |  | Single payload or iterable of payloads. Anything that is not a solar/thermal payload (no `time_period` / no `latitude`) is silently skipped. | *required* |
| `polygon_centroid_lat` | `Optional[float]` | Fallback location if a payload itself lacks latitude/longitude. | `None` |
| `mode` | `str` | `"off"` — function is a no-op (still won't raise). `"log"` — emit `logger.warning` for warning/critical results. `"raise"` — additionally raise `PreflightWarning` for warning/critical results. Any other value is treated as `"log"`. | `'log'` |
| `logger` |  | Optional `logging.Logger` used for the warning. Falls back to the module logger when `None`. | `None` |
| `buffer_m` | `float` | Effective per-cell context buffer for the active tiling config. Default `128` matches `SOLAR_TILING_CONFIG`'s margin. | `128.0` |

## solar_elevation_deg

```python
solar_elevation_deg(lat_deg: float, doy: int, solar_hour: float) -> float
```

Return the approximate solar elevation in degrees.

Uses a sinusoidal declination model (`δ ≈ 23.45° · sin(360°/365 · (n − 81))`) and the standard altitude formula. Equation-of-time and atmospheric refraction are intentionally not modelled — accuracy is well within the ~5° tolerance required for a pre-flight gate.

Returns `nan` for a non-finite `solar_hour` (a literal `+inf` or `-inf`) rather than raising.
