TileService

Generate a grid of tiles covering a GeoJSON polygon.

Parameters:

NameTypeDescriptionDefault
polygondictA GeoJSON Polygon object ({"type": "Polygon", "coordinates": ...}). Validated and winding-order-normalized on construction.required
loggerLoggerPython logger instance.required
analysis_typeAnalysesNameReserved for future per-type tile size differentiation.None
max_tiles_overrideintOverride the default maximum number of non-empty tiles (100 by default). None keeps the default.None

polygon instance-attribute

Python
polygon: dict = validate_polygon(polygon)

Validated GeoJSON Polygon.

points instance-attribute

Python
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

Python
logger: Logger = logger

Logger passed to the service.

analysis_type instance-attribute

Python
analysis_type: Optional[Union[AnalysesName, str]] = analysis_type

Analysis type the grid is generated for, or None.

tile_size instance-attribute

Python
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

Python
max_non_empty: Optional[int] = max_tiles_override

The caller's override of the maximum non-empty tiles, or None.

config instance-attribute

Python
config: TilingConfig = get_tiling_config(
str(analysis_type) if analysis_type else None
)

Tiling configuration for the analysis type.

generate_tiles_for_polygon

Python
generate_tiles_for_polygon() -> List[List[Tile]]

Generate a grid of tiles covering the polygon's bounding box.

Returns:

TypeDescription
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:

TypeDescription
PolygonValidationErrorIf 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:

NameTypeDescription
tile_countintNumber of tiles covering the polygon.
estimated_time_sfloatEstimated run time in seconds.
estimated_cost_tokensfloatEstimated cost in tokens: would_bill_jobs times tokens_per_job.
would_bill_jobsint or NoneNumber of jobs the run would submit and bill.
sensor_countint or NoneTotal sensors across the planned batches. None for a grid analysis (no facade batching) or when payload= was omitted.
tokens_per_jobfloat or NoneThe price of one job from the API price list.
pricing_versionstr or NoneThe version of the price list, when it names one.
pricing_sourcestr 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:

NameTypeDescription
merged_gridAnyThe 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_divisorintint16 grids only: physical value = stored value / value_divisor (10 for UTCI/TCI). 1 for the float dtypes.
validndarray or Noneint16 grids only: the validity bitmap (uint8, one bit per cell in row-major order, least significant bit first). None for the float dtypes.
polygondictThe source GeoJSON polygon.
analysis_typestrWhich analysis type was run.
grid_shapetuple[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_jobslist[str]Job IDs where the job status was Failed.
skipped_jobslist[str]Job IDs where download failed (succeeded job but network error), or jobs that were still pending/running when merge was called.
total_jobsintTotal number of jobs.
succeeded_jobsintNumber of jobs that succeeded.
min_legend, max_legendfloat or NoneThe 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.
legendlist[str] or NoneThe 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_tileslist[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

Python
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:

TypeDescription
dictA JSON-compatible dict that from_dict() turns back into an equivalent result.

from_dict classmethod

Python
from_dict(data: dict) -> AreaResult

Reconstruct from a dict produced by to_dict().

Unknown keys are silently ignored for forward compatibility.

Parameters:

NameTypeDescriptionDefault
datadictA dict produced by to_dict().required

Returns:

TypeDescription
AreaResultThe reconstructed result. merged_grid has the stored value_dtype (float64 for a dict without one).

Raises:

TypeDescription
KeyErrorIf 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:

NameTypeDescription
jobsdict[str, str]Mapping of tile_id to job_id.
polygondictThe source GeoJSON polygon.
analysis_typestrWhich analysis type was run.
config_hashstrSHA-256 hash of the payload's config-relevant fields.
tile_positionsdict[str, tuple[int, int]]Mapping of tile_id to (row, col) grid position.
grid_shapetuple[int, int](num_rows, num_cols) of the tile grid.
webhook_urlstr or NoneWebhook URL used for this submission.
webhook_eventstuple[str, ...] or NoneWebhook events subscribed to.
failed_submissionstuple[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_submissionstuple[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.
configTilingConfig or NoneThe tiling configuration (tile size, step and context margins) the tiles were built with.
submission_abort_statusint or NoneHTTP 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_fieldsboolTrue 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_hashstr or NoneFingerprint 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_mfloat or NoneThe 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_jobfloat or NoneThe 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_versionint or NoneFacade 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_versionint or NoneVersion 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_membershipdict[str, tuple[str, ...]] or NoneBuilding IDs in each facade batch, keyed by batch key ({tile_id}#batch{i}). None on a grid schedule.
batch_sensor_countsdict[str, int] or NoneRetained sensor count of each facade batch, keyed by batch key. None on a grid schedule.
transportstr or NoneTransport the jobs were submitted with ("json" or "binary"). None on schedules built before the field existed.
wire_versionint or NoneBinary wire version of the submission (1 for the binary transport), None for a JSON submission.
binary_acknowledgementsdict[str, dict] or NoneThe server's acknowledgement of each binary submission, keyed by schedule key (tile ID or batch key). None for a JSON submission.
weather_identitystr or NoneThe 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_versionint or NoneVersion 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_identitystr or NoneDigest 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_idstr or NoneRandom ID made once per schedule and kept across every retry of it. Client-side only; never sent.
attemptsdict[str, int] or NoneAttempt number of the current job or submission of each schedule key; a missing key is attempt 1. Client-side only; never sent.
submission_errorsdict[str, str] or NoneWhy 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.
failuresdict[str, dict] or NoneWhy 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

Python
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:

NameTypeDescriptionDefault
otherAreaScheduleThe retry schedule to merge in.required

Returns:

TypeDescription
AreaScheduleA new merged schedule.

Raises:

TypeDescription
ValueErrorIf polygon, analysis_type, or config_hash do not match.

to_dict

Python
to_dict() -> dict

Serialise to a plain dict for persistence.

Tuple fields are converted to lists for JSON compatibility.

Returns:

TypeDescription
dictA JSON-compatible dict that from_dict() turns back into an equal schedule.

from_dict classmethod

Python
from_dict(data: dict) -> AreaSchedule

Reconstruct from a dict produced by to_dict().

Unknown keys are silently ignored for forward compatibility.

Parameters:

NameTypeDescriptionDefault
datadictA dict produced by to_dict().required

Returns:

TypeDescription
AreaScheduleThe reconstructed schedule.

Raises:

TypeDescription
KeyErrorIf a required key (jobs, polygon, analysis_type, config_hash or grid_shape) is missing.
ValueErrorIf 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:

NameTypeDescription
statusstrDerived status: "empty" (no jobs), "pending", "running", "completed", "failed", or "partial".
job_statesdict[str, JobStatus]Mapping of job_id to its current status.
succeededintCount of succeeded jobs.
failedintCount of failed jobs.
runningintCount of running jobs.
pendingintCount of pending jobs.
totalintTotal number of jobs.
is_completeboolTrue 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.
uncertainintCount 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

Python
from_job_states(
job_states: Dict[str, JobStatus], *, uncertain: int = 0
) -> AreaState

Compute 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:

NameTypeDescriptionDefault
job_statesdict[str, JobStatus]Mapping of job_id to its current status.required
uncertainintNumber of tiles whose submission outcome is unknown. Default 0.0

Returns:

TypeDescription
AreaStateThe derived snapshot.

AreaTimeoutError

Bases: Exception

Raised when run_area_and_wait exceeds its area_timeout.

Attributes:

NameTypeDescription
area_stateAreaStateThe state snapshot at the time of timeout.
messagestrHuman-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:

NameTypeDescription
tileslist[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_rowsintNumber of rows in the grid.
num_colsintNumber of columns in the grid.
polygondictThe source GeoJSON polygon used to generate this grid.
configTilingConfig or NoneThe tiling configuration (tile size, step and context margins) the grid was generated with.

non_empty_tiles property

Python
non_empty_tiles: List[Tuple[int, int, Tile]]

Return non-empty tiles with their grid positions.

Returns:

TypeDescription
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:

NameTypeDescription
merged_gridAnyMerged, 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_divisorintint16 grids only: physical value = stored value / value_divisor. 1 for the float dtypes.
validndarray or Noneint16 grids only: the validity bitmap (uint8, one bit per cell, least significant bit first). None for the float dtypes, where NaN is no value.
polygondictOriginal input GeoJSON polygon.
tile_countintNumber of tiles that were executed.
grid_shapetuple[int, int](rows, cols) of the merged grid.
failed_tileslist[TileFailure]Tiles that failed after all retries.
skipped_tileslist[str]Tile IDs that were skipped.
execution_timefloatTotal wall-clock seconds for the tiled run.
per_tile_resultslist[[TileResult](#infrared_sdk.tiling.TileResult)]Raw per-tile results.
analysis_typestrWhich analysis type was run.
min_legend, max_legendfloat or NoneThe 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.
legendlist[str] or NoneThe 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:

NameTypeDescription
tile_idstrUnique identifier of the tile.
rowintRow index in the tile grid.
colintColumn index in the tile grid.
datadict or NoneThe raw result payload for the tile, or None when the tile produced no result.