TileService
Generate a grid of tiles covering a GeoJSON polygon.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
polygon | dict | A GeoJSON Polygon object ({"type": "Polygon", "coordinates": ...}). Validated and winding-order-normalized on construction. | required |
logger | Logger | Python logger instance. | required |
analysis_type | AnalysesName | Reserved for future per-type tile size differentiation. | None |
max_tiles_override | int | Override the default maximum number of non-empty tiles (100 by default). None keeps the default. | None |
polygon instance-attribute
polygon: dict = validate_polygon(polygon)
Validated GeoJSON Polygon.
points instance-attribute
points: List[Point] = [ Point(latitude=pos[1], longitude=pos[0]) for pos in ring[:-1] ]
Internal polygon vertices as Point objects (lat/lon named fields).
logger instance-attribute
logger: Logger = logger
Logger passed to the service.
analysis_type instance-attribute
analysis_type: Optional[Union[AnalysesName, str]] = analysis_type
Analysis type the grid is generated for, or None.
tile_size instance-attribute
tile_size: TileSize = TileSize( x=self.config.inference_size_m, y=self.config.inference_size_m )
Tile size in meters, from the tiling config.
max_non_empty instance-attribute
max_non_empty: Optional[int] = max_tiles_override
The caller's override of the maximum non-empty tiles, or None.
config instance-attribute
config: TilingConfig = get_tiling_config( str(analysis_type) if analysis_type else None )
Tiling configuration for the analysis type.
generate_tiles_for_polygon
generate_tiles_for_polygon() -> List[List[Tile]]
Generate a grid of tiles covering the polygon's bounding box.
Returns:
| Type | Description |
|---|---|
list[list[Tile]] | 2-D grid where tiles[row][col] is a Tile. Row 0 is the southernmost row; column 0 is the westernmost column. Each tile is marked empty=True if it does not intersect the polygon (neither centroid inside nor bbox overlap). |
Raises:
| Type | Description |
|---|---|
PolygonValidationError | If the number of non-empty tiles exceeds the cap in force. The message names the tile count, the cap, the grid family and the max_tiles_override value that would clear it. |
AreaPreview dataclass
Preview and cost estimate for an area analysis before submission.
would_bill_jobs, estimated_time_s and estimated_cost_tokens are priced from the planned number of jobs, never from tile_count alone. Grid analyses submit one job per tile, so would_bill_jobs == tile_count there. A facade (analysis_surfaces) run can split a tile into several billed sub-batches when its estimated sensor count exceeds the server cap. Passing payload= (with buildings= if the geometry is not embedded) runs the same offline batch split run_area submits, so would_bill_jobs reflects the real job count before any HTTP call.
Warnings
Without payload=, would_bill_jobs falls back to tile_count, which is safe when there is no facade geometry to preview yet but an under-estimate for a facade payload.
Attributes:
| Name | Type | Description |
|---|---|---|
tile_count | int | Number of tiles covering the polygon. |
estimated_time_s | float | Estimated run time in seconds. |
estimated_cost_tokens | float | Estimated cost in tokens: would_bill_jobs times tokens_per_job. |
would_bill_jobs | int or None | Number of jobs the run would submit and bill. |
sensor_count | int or None | Total sensors across the planned batches. None for a grid analysis (no facade batching) or when payload= was omitted. |
tokens_per_job | float or None | The price of one job from the API price list. |
pricing_version | str or None | The version of the price list, when it names one. |
pricing_source | str or None | "live", "cached" (fetched less than 20 minutes ago) or "stale" (the fetch failed and the last list was used). |
AreaResult dataclass
Bases: _PhysicalGridMixin
Public result from an area analysis.
Attributes:
| Name | Type | Description | |
|---|---|---|---|
merged_grid | Any | The merged grid, a READ-ONLY numpy array in the wire dtype of the run: float16 (solar radiation, sky view factor, thermal comfort statistics, wind), float32 (direct sun hours, daylight availability, and the class codes of a categorical result), int16 (UTCI saved before 2026-10-07; UTCI is float16 since then: stored value = physical value x value_divisor), or float64 (a run that mixes dtypes). NaN is no value for the float dtypes; valid tells it for int16. Use physical_grid() for physical values: an old int16 UTCI grid read without the divisor is 10 times too large; it handles both. | |
value_divisor | int | int16 grids only: physical value = stored value / value_divisor (10 for UTCI/TCI). 1 for the float dtypes. | |
valid | ndarray or None | int16 grids only: the validity bitmap (uint8, one bit per cell in row-major order, least significant bit first). None for the float dtypes. | |
polygon | dict | The source GeoJSON polygon. | |
analysis_type | str | Which analysis type was run. | |
grid_shape | tuple[int, int] | (num_rows, num_cols) of the merged grid. | |
bounds | `tuple[float, float, float, float] \ | None` | Geographic extent of merged_grid as (min_lng, min_lat, max_lng, max_lat). When the input polygon is not an integer multiple of step_m, the actual grid extent is padded north and east to the next step boundary; this field reports that true extent so visualisers can place the bitmap without a SW-anchored squash. None when no grid was produced (e.g. empty schedule). |
failed_jobs | list[str] | Job IDs where the job status was Failed. | |
skipped_jobs | list[str] | Job IDs where download failed (succeeded job but network error), or jobs that were still pending/running when merge was called. | |
total_jobs | int | Total number of jobs. | |
succeeded_jobs | int | Number of jobs that succeeded. | |
min_legend, max_legend | float or None | The exact value range measured over merged_grid, not a fold of the per-tile backend estimates. None for a categorical result, an empty grid, or when the grid holds no finite value. | |
legend | list[str] or None | The class labels of a categorical result (wind-comfort classes), sorted and indexed by the class codes in merged_grid. None for a numeric result. | |
failed_tiles | list[TileFailure] | Per-tile failure records classified by TileFailurePhase (submit / compute / download / skipped). Same tile_id never appears twice; first-seen phase wins (submit > compute > download > skipped). Empty when every tile produced usable output. |
to_dict
to_dict() -> dict
Serialise to a plain dict.
merged_grid holds the PHYSICAL values as a nested list (None for no value), as before 1.0.0: a dict reader needs no divisor. value_dtype and value_divisor record the wire dtype, so from_dict() gives back the same dtype and the same values.
Returns:
| Type | Description |
|---|---|
dict | A JSON-compatible dict that from_dict() turns back into an equivalent result. |
from_dict classmethod
from_dict(data: dict) -> AreaResult
Reconstruct from a dict produced by to_dict().
Unknown keys are silently ignored for forward compatibility.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data | dict | A dict produced by to_dict(). | required |
Returns:
| Type | Description |
|---|---|
AreaResult | The reconstructed result. merged_grid has the stored value_dtype (float64 for a dict without one). |
Raises:
| Type | Description |
|---|---|
KeyError | If a required key (polygon, analysis_type or grid_shape) is missing. |
AreaSchedule dataclass
Schedule of submitted jobs for an area analysis.
Produced by run_area() and consumed by check_area_state(), merge_area_jobs(), and run_area_and_wait(). Use to_dict() and from_dict() to persist a schedule and resume it later.
Attributes:
| Name | Type | Description |
|---|---|---|
jobs | dict[str, str] | Mapping of tile_id to job_id. |
polygon | dict | The source GeoJSON polygon. |
analysis_type | str | Which analysis type was run. |
config_hash | str | SHA-256 hash of the payload's config-relevant fields. |
tile_positions | dict[str, tuple[int, int]] | Mapping of tile_id to (row, col) grid position. |
grid_shape | tuple[int, int] | (num_rows, num_cols) of the tile grid. |
webhook_url | str or None | Webhook URL used for this submission. |
webhook_events | tuple[str, ...] or None | Webhook events subscribed to. |
failed_submissions | tuple[str, ...] | Tile IDs where job submission failed in a way that is SAFE to retry -- the server either never saw the request or answered with a definitive rejection. |
uncertain_submissions | tuple[str, ...] | Tile IDs whose submission outcome is UNKNOWN: the request may already have been accepted and queued server-side (a lost/timed-out response, or a 2xx reply whose body didn't parse into a job). Deliberately a SEPARATE bucket from failed_submissions, not a variant of it: run_area(..., retry_from=schedule) builds its resubmission set from failed_submissions alone, so an uncertain tile is never automatically resubmitted -- the SDK cannot tell whether that would create a second billed job, so it does not decide for the caller. Resolving one requires a deliberate step outside this field (check job history for the tile, then start a fresh, non-retry submission if nothing exists). It is carried forward unchanged across a retry_from run and can never appear in a retry's own result. |
config | TilingConfig or None | The tiling configuration (tile size, step and context margins) the tiles were built with. |
submission_abort_status | int or None | HTTP status that fail-fasted the submission batch, or None when submission ran to completion. Currently only 402 (insufficient credits): the condition is account-global, so the SDK stops submitting remaining tiles after the first one — those tiles land in failed_submissions without an HTTP call. Consumers should treat 402 as "don't retry, top up credits"; plain failed_submissions entries with status None keep their usual transient-retry semantics. |
surface_fields | bool | True when the submitted payload carried facade or bring-your-own-sensor fields (analysis_surfaces / sensor_points); the merge uses it to choose the surface merge. |
buildings_hash | str or None | Fingerprint of the buildings/geometries map that produced this schedule. Geometry drives sub-tile batch boundaries but is not part of config_hash, so the retry guard compares this separately. None on schedules built before the field existed. |
terrain_context_margin_m | float or None | The terrain reach these tiles were sliced to, in metres — the FLOORED value actually used (max(config.context_margin_m, requested)), not the value passed to run_area. So a default solar run reads back 128.0, not 0.0. Guarded on retry and on merge. None on schedules built before the field existed. |
max_sensors_per_job | float or None | The facade per-job cap that planned this schedule. Its unit depends on batching_policy_version: on an exact v2 schedule it is the caller's cap in RETAINED sensors, None for the default target; on a legacy v1 schedule it is the resolved estimate-policy cap. A missing saved policy version means legacy v1 for compatibility. Guarded on retry and on merge when present. |
batching_policy_version | int or None | Facade batching policy that planned the batches: 2 is the exact retained-sensor policy (it requires complete batch_membership and batch_sensor_counts); None or 1 is the legacy estimate policy. |
batch_count_contract_version | int or None | Version of the sensor-counting rules the batch sensor counts were computed under. A retry of a schedule counted under an older version is refused by name. |
batch_membership | dict[str, tuple[str, ...]] or None | Building IDs in each facade batch, keyed by batch key ({tile_id}#batch{i}). None on a grid schedule. |
batch_sensor_counts | dict[str, int] or None | Retained sensor count of each facade batch, keyed by batch key. None on a grid schedule. |
transport | str or None | Transport the jobs were submitted with ("json" or "binary"). None on schedules built before the field existed. |
wire_version | int or None | Binary wire version of the submission (1 for the binary transport), None for a JSON submission. |
binary_acknowledgements | dict[str, dict] or None | The server's acknowledgement of each binary submission, keyed by schedule key (tile ID or batch key). None for a JSON submission. |
weather_identity | str or None | The weather these tiles submitted — the columns, the payload latitude and longitude and the window — as a digest, or None for an analysis that reads no weather array. config_hash covers none of that, so it alone tells two runs of one window against two climates apart. Guarded on retry and merge; never sent. |
schedule_contract_version | int or None | Version of this client-side record; 4 is the run identity described under weather_identity. Versions 2 and 3 held an EPW FILE identity and are refused by name on a weather-bearing resume. None predates the field. |
site_identity | str or None | Digest of the raw site inputs (buildings with their per-building overrides, trees, ground materials, context and terrain geometry). Guarded on retry and merge; never sent. None on schedules built before the field existed. |
run_id | str or None | Random ID made once per schedule and kept across every retry of it. Client-side only; never sent. |
attempts | dict[str, int] or None | Attempt number of the current job or submission of each schedule key; a missing key is attempt 1. Client-side only; never sent. |
submission_errors | dict[str, str] or None | Why a key has no result, by schedule key: why the SDK stopped its submission (for example a tile the encoder refused, or HTTP 429 until the area timeout), or the server's error text of its failed job. Only keys that have a reason. Client-side only; never sent. |
failures | dict[str, dict] or None | Why a key has no result, by schedule key: {"kind": ..., "status": ...} (status only when an HTTP answer decided it). The source of TileFailure.kind and TileFailure.status. Client-side only; never sent. |
merge
merge(other: AreaSchedule) -> AreaSchedule
Merge another schedule into this one (e.g. after retry).
Validates that both schedules refer to the same polygon (via fingerprint), analysis_type, and config_hash. Overlapping tile_ids in other override this schedule's jobs. Failures that are now covered by other are removed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other | AreaSchedule | The retry schedule to merge in. | required |
Returns:
| Type | Description |
|---|---|
AreaSchedule | A new merged schedule. |
Raises:
| Type | Description |
|---|---|
ValueError | If polygon, analysis_type, or config_hash do not match. |
to_dict
to_dict() -> dict
Serialise to a plain dict for persistence.
Tuple fields are converted to lists for JSON compatibility.
Returns:
| Type | Description |
|---|---|
dict | A JSON-compatible dict that from_dict() turns back into an equal schedule. |
from_dict classmethod
from_dict(data: dict) -> AreaSchedule
Reconstruct from a dict produced by to_dict().
Unknown keys are silently ignored for forward compatibility.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data | dict | A dict produced by to_dict(). | required |
Returns:
| Type | Description |
|---|---|
AreaSchedule | The reconstructed schedule. |
Raises:
| Type | Description |
|---|---|
KeyError | If a required key (jobs, polygon, analysis_type, config_hash or grid_shape) is missing. |
ValueError | If the saved batch records are inconsistent. |
AreaState dataclass
Snapshot of area-level job status.
Produced by check_area_state() and consumed by run_area_and_wait() callbacks.
Attributes:
| Name | Type | Description |
|---|---|---|
status | str | Derived status: "empty" (no jobs), "pending", "running", "completed", "failed", or "partial". |
job_states | dict[str, JobStatus] | Mapping of job_id to its current status. |
succeeded | int | Count of succeeded jobs. |
failed | int | Count of failed jobs. |
running | int | Count of running jobs. |
pending | int | Count of pending jobs. |
total | int | Total number of jobs. |
is_complete | bool | True when all jobs are in a terminal state. Also True when the schedule has no job but has uncertain tiles: nothing is in flight and an uncertain tile is never resubmitted, so waiting cannot change the state. The merge then reports each uncertain tile. |
uncertain | int | Count of tiles whose submission outcome is unknown: the SDK has no job id to poll for them. The merge reports each as a TileFailure; resolve them through job history. |
from_job_states classmethod
from_job_states(
job_states: Dict[str, JobStatus], *, uncertain: int = 0
) -> AreaStateCompute an AreaState from a mapping of job_id to JobStatus.
Status priority: 1. If no jobs -> "empty" (distinct from "completed"; submission may have failed wholesale or been short-circuited) 2. If all jobs pending -> "pending" 3. If any non-terminal job exists (pending or running) -> "running" 4. If all terminal and all succeeded -> "completed", or "partial" when a submission is uncertain 5. If all terminal and all failed -> "failed" 6. If all terminal with mixed results -> "partial"
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
job_states | dict[str, JobStatus] | Mapping of job_id to its current status. | required |
uncertain | int | Number of tiles whose submission outcome is unknown. Default 0. | 0 |
Returns:
| Type | Description |
|---|---|
AreaState | The derived snapshot. |
AreaTimeoutError
Bases: Exception
Raised when run_area_and_wait exceeds its area_timeout.
Attributes:
| Name | Type | Description |
|---|---|---|
area_state | AreaState | The state snapshot at the time of timeout. |
message | str | Human-readable timeout message. |
TileGrid dataclass
Immutable wrapper around a 2-D grid of tiles.
Produced by tile generation for a polygon and consumed by downstream composable methods (run_area, get_by_tiles).
Tile IDs are deterministic UUIDs derived from the polygon geometry and grid position — the same polygon always produces the same IDs.
Attributes:
| Name | Type | Description |
|---|---|---|
tiles | list[list[Tile]] | 2-D grid where tiles[row][col] is a Tile. Row 0 is the southernmost row; column 0 is the westernmost column. |
num_rows | int | Number of rows in the grid. |
num_cols | int | Number of columns in the grid. |
polygon | dict | The source GeoJSON polygon used to generate this grid. |
config | TilingConfig or None | The tiling configuration (tile size, step and context margins) the grid was generated with. |
non_empty_tiles property
non_empty_tiles: List[Tuple[int, int, Tile]]
Return non-empty tiles with their grid positions.
Returns:
| Type | Description |
|---|---|
list[tuple[int, int, Tile]] | Each element is (row, col, tile) for tiles where tile.empty is False. |
TiledResult dataclass
Bases: _PhysicalGridMixin
Aggregated result from a tiled analysis run.
Attributes:
| Name | Type | Description |
|---|---|---|
merged_grid | Any | Merged, blended, clipped grid: a READ-ONLY numpy array in the wire dtype of the run (float16, float32, int16 or float64; see AreaResult.merged_grid). Use physical_grid() for physical values. Typed as Any to avoid import-time numpy dependency. |
value_divisor | int | int16 grids only: physical value = stored value / value_divisor. 1 for the float dtypes. |
valid | ndarray or None | int16 grids only: the validity bitmap (uint8, one bit per cell, least significant bit first). None for the float dtypes, where NaN is no value. |
polygon | dict | Original input GeoJSON polygon. |
tile_count | int | Number of tiles that were executed. |
grid_shape | tuple[int, int] | (rows, cols) of the merged grid. |
failed_tiles | list[TileFailure] | Tiles that failed after all retries. |
skipped_tiles | list[str] | Tile IDs that were skipped. |
execution_time | float | Total wall-clock seconds for the tiled run. |
per_tile_results | list[[TileResult](#infrared_sdk.tiling.TileResult)] | Raw per-tile results. |
analysis_type | str | Which analysis type was run. |
min_legend, max_legend | float or None | The exact value range measured over merged_grid, not a fold of the per-tile backend estimates. None for a categorical result, an empty grid, or when the grid holds no finite value. |
legend | list[str] or None | The class labels of a categorical result (wind-comfort classes), sorted and indexed by the class codes in merged_grid. None for a numeric result. |
TileResult dataclass
Raw result from a single analysis tile.
Attributes:
| Name | Type | Description |
|---|---|---|
tile_id | str | Unique identifier of the tile. |
row | int | Row index in the tile grid. |
col | int | Column index in the tile grid. |
data | dict or None | The raw result payload for the tile, or None when the tile produced no result. |