Index Specification
This reference covers the public types used to declare and resolve direct-Zarr index specs. TimeAxis provides the recommended intent-named constructors for time axes. IndexSpec, RegularTimeAxis, IntegerAxis, and IrregularTimeAxis describe the layout. AUTO is the sentinel that tells the engine to discover coordinates at planning time. inspect_item() returns ItemInfo, and resolve_index_spec() turns the declarative spec into a cached ResolvedIndex for the run.
coerce_to_epoch_s() normalizes supported timestamp values to Unix epoch seconds.
ResolvedIndexRecord is the on-disk control-plane record written after the engine resolves an IndexSpec.
| Name | Description |
|---|---|
TimeAxis |
Intent-named constructors for time-axis declarations. |
AxisSpec |
Marker base for axis specifications. |
IndexSpec |
Declarative index specification for a multi-group DirectZarr product. |
IntegerAxis |
A zero-based integer axis with a fixed slot count. |
IrregularTimeAxis |
A time axis with explicit coordinate values. |
AUTO |
Sentinel that tells IrregularTimeAxis to discover coordinate values at planning time. |
RegularTimeAxis |
A regularly-spaced time axis with a fixed epoch and cadence. |
ItemInfo |
Metadata returned by inspect_item describing a single source item. |
ResolvedIndex |
A resolved, immutable index for a multi-group DirectZarr product. |
ResolvedIndexRecord |
On-disk record for an engine-resolved index payload. |
coerce_to_epoch_s |
Coerce a coordinate value to seconds since the Unix epoch (UTC). |
resolve_index_spec |
Resolve an IndexSpec into a ResolvedIndex. |
Index Types
Import these from firecube.ingestor.api.
firecube.ingestor.api.TimeAxis
Intent-named constructors for time-axis declarations.
The four constructors cover every supported time-axis shape and are the
recommended way to declare one; each returns a plain axis dataclass, so
the explicit RegularTimeAxis / IrregularTimeAxis forms remain
available as an escape hatch.
Pick by answering one question -- what defines your product's timeline?
- A fixed cadence, and the coordinate should carry the grid labels:
:meth:
grid. - A fixed cadence, but the coordinate should carry each slot's real
observation time: :meth:
observed. - A known list of timestamps with no fixed cadence: :meth:
explicit. - Timestamps only your source items can reveal: :meth:
discovered.
Examples:
>>> axis = TimeAxis.observed(
... coordinate="time",
... epoch="2025-07-01T00:00:00Z",
... cadence_s=600,
... slot_count=4320,
... )
>>> spec = IndexSpec(name="my_product_v1", groups={"data": axis})
Methods:
grid
staticmethod
A fixed-cadence axis whose coordinate carries the grid labels.
The coordinate values are epoch + n * cadence_s, known before
ingest, so the engine materializes and seals them at preallocate.
Item timestamps must sit exactly on grid boundaries; off-grid
observation times need :meth:observed instead, because sealed grid
values cannot pass drift verification against off-grid writes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coordinate
|
str
|
Name of the time coordinate dimension. |
required |
epoch
|
str
|
UTC-explicit ISO 8601 axis origin. |
required |
cadence_s
|
int
|
Slot cadence in seconds (positive integer). |
required |
slot_count
|
int | None
|
Total slots; mutually exclusive with |
None
|
end_date
|
str | None
|
Exclusive UTC-explicit axis end; mutually exclusive
with |
None
|
placement
|
Literal['exact', 'floor']
|
Must be |
'exact'
|
observed
staticmethod
A fixed-cadence axis whose coordinate carries real observation times.
Items are placed on the grid by flooring, but the coordinate stores
each slot's actual observation time (for example a sensing start such
as 00:00:02 in a ten-minute cadence). Those values only exist once
the source items are known, so the engine writes them in a
single-writer materialization step; ingest verifies them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coordinate
|
str
|
Name of the time coordinate dimension. |
required |
epoch
|
str
|
UTC-explicit ISO 8601 axis origin. |
required |
cadence_s
|
int
|
Slot cadence in seconds (positive integer). |
required |
slot_count
|
int | None
|
Total slots; mutually exclusive with |
None
|
end_date
|
str | None
|
Exclusive UTC-explicit axis end; mutually exclusive
with |
None
|
explicit
staticmethod
An axis whose timeline is a known, explicit list of timestamps.
Use when the product has no fixed cadence but the full timeline is
known when the plugin is configured. The engine sorts the values
ascending, assigns slot indices in that order, and materializes the
coordinate at preallocate. The rest of the plugin is unchanged:
resolved_index(ctx).position(group, timestamp) maps each value to
its declared slot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coordinate
|
str
|
Name of the time coordinate dimension. |
required |
values
|
Sequence[Any]
|
Non-empty sequence of distinct coordinate values
( |
required |
Examples:
discovered
staticmethod
An axis whose timeline is discovered from the source items.
Use when only the inputs can reveal the timestamps. Before
preallocate the engine calls inspect_item on every discovered
source item, sorts the coordinates ascending, and freezes the
resulting axis. That costs one full source pass at planning time.
The declaration is IrregularTimeAxis(values=AUTO), the spelling
error messages use.
Discovery makes inspect_item load-bearing:
- Return
ItemInfo(coordinate=timestamp)for every item that belongs on the axis. - Return
Noneto skip an item entirely. - Return
ItemInfo(coordinate=None)only when the item exists but its coordinate cannot be resolved; discovery fails withMissingIrregularCoordinateErrorfor it.
inspect_item runs during discovery and again during the write
phase, so it must be idempotent and independent of discovery order,
and items must resolve through references that stay valid for the
run because the discovered axis is handed to parallel workers.
Duplicate coordinates raise DuplicateIrregularCoordinateError;
zero discovered items raise NoDiscoveredItemsError.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coordinate
|
str
|
Name of the time coordinate dimension. |
required |
firecube.ingestor.api.AxisSpec
Marker base for axis specifications.
RegularTimeAxis, IntegerAxis, and IrregularTimeAxis are the
current implementations. Additional axis types are planned as additive
extensions in future releases. The resolver in index_resolve.py
dispatches on isinstance -- no abstract methods are required here.
firecube.ingestor.api.IndexSpec
dataclass
Declarative index specification for a multi-group DirectZarr product.
When groups has more than one entry and the ingestion caller does not
pass slot_group to name one authoritative group, every group must
reference the same axis object (Python is identity, not value
equality): slot-range filtering operates on one canonical axis, and two
structurally equal axes give the engine no way to pick one. Separately
constructed axes with identical fields are rejected with
ConfigurationError at bind time; share the instance instead, or pass
--slot-group on the command line.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Stable identifier for this index configuration. Used as the
|
groups |
Mapping[str, AxisSpec]
|
Mapping from group name to axis specification. Must be non-empty. Normalized to a sorted tuple internally for hashability. |
time_unit |
str | None
|
Optional time unit string forwarded to the legacy
|
Examples:
firecube.ingestor.api.IntegerAxis
dataclass
Bases: AxisSpec
A zero-based integer axis with a fixed slot count.
Declares an integer position with no epoch or cadence. inspect_item
returns the integer position as the item's coordinate, and
resolved_index(ctx).position(group, key) maps it to the slot index.
A single IndexSpec may mix IntegerAxis and time-axis groups;
each group resolves its own axis independently.
Attributes:
| Name | Type | Description |
|---|---|---|
slot_count |
int
|
Total number of integer positions on the axis
(positive integer). Sets the shape of the axis dimension
in the preallocated Zarr store: arrays indexed by this
axis are created with shape |
Examples:
firecube.ingestor.api.IrregularTimeAxis
dataclass
Bases: AxisSpec
A time axis with explicit coordinate values.
Attributes:
| Name | Type | Description |
|---|---|---|
coordinate |
str
|
Name of the time coordinate dimension. Must match
|
values |
Sequence[Any] | _AutoSentinel
|
Explicit coordinate values for the axis, or |
Note
AUTO triggers planning-time discovery: the engine scans the full
source set via inspect_item before preallocate so it can discover
coordinates and sort them. That costs a full source pass.
firecube.ingestor.api.AUTO
module-attribute
Sentinel that tells IrregularTimeAxis to discover coordinate values at planning time.
Set IrregularTimeAxis(coordinate=..., values=AUTO) to let the engine call
inspect_item on every source item before preallocate and build the axis
from the returned coordinates. The engine sorts discovered coordinates and
assigns each a slot index in ascending order.
Importable from firecube.core.api and firecube.ingestor.api.
firecube.ingestor.api.RegularTimeAxis
dataclass
Bases: AxisSpec
A regularly-spaced time axis with a fixed epoch and cadence.
Attributes:
| Name | Type | Description |
|---|---|---|
coordinate |
str
|
Name of the time coordinate dimension (e.g. |
epoch |
str
|
UTC-explicit ISO 8601 string for the axis origin (e.g.
|
cadence_s |
int
|
Slot cadence in seconds. Must be a positive integer. |
mode |
Literal['exact', 'floor']
|
Alignment mode. |
end_date |
str | None
|
Optional UTC-explicit ISO 8601 string for the axis end
(exclusive). Must be aligned to the cadence and strictly after
|
slot_count |
int | None
|
Optional total number of slots. Must be positive.
At most one of |
Note
Both end_date and slot_count may be None for serial-mode
plugins that do not declare a fixed horizon. The parallel gate will
raise ConfigurationError if it cannot determine the extent.
Attributes
end_date
class-attribute
instance-attribute
UTC-explicit ISO 8601 timestamp for the axis end (exclusive).
The last written slot's left edge is at end_date - cadence_s; the axis
covers the half-open interval [epoch, end_date).
Must be strictly after epoch and aligned to cadence_s (i.e.
(end_date - epoch) must be a whole multiple of cadence_s).
Misalignment raises ValueError with the two nearest aligned
boundaries in the error message.
Mutually exclusive with slot_count. Leave both None for
serial-mode plugins without a fixed horizon.
slot_count
class-attribute
instance-attribute
Total number of slots on the axis (positive integer).
Equivalent to (end_date - epoch) // cadence_s when end_date is
provided instead; the two spellings are algebraically identical, pick
whichever expresses the product horizon most naturally.
Sets the shape of the time dimension in the preallocated Zarr store:
time-indexed arrays are created with shape (slot_count, ...).
Mutually exclusive with end_date. Leave both None for
serial-mode plugins without a fixed horizon.
firecube.ingestor.api.ItemInfo
dataclass
Metadata returned by inspect_item describing a single source item.
The engine uses coordinate to map the item to a position on its
assigned axis.
Attributes:
| Name | Type | Description |
|---|---|---|
coordinate |
Any
|
The item's coordinate value. For |
firecube.ingestor.api.ResolvedIndex
A resolved, immutable index for a multi-group DirectZarr product.
Built by resolve_index_spec and cached per run context and spec on
the DirectZarrIngestor instance, so repeated lookups within a run
return the same object.
Plugin code reaches it through resolved_index(ctx). Use
size(group) when a schema needs the declared axis extent before
writes begin, position(group, coordinate) when a write needs the
slot index for a timestamp or integer coordinate, and
coordinate(group, index) for the reverse lookup.
The identity_hash is content-addressed from the canonical
resolved-index payload. It intentionally does not track the legacy
slot-index model hash.
Attributes
identity_hash
cached
property
Content-addressed hash of the canonical resolved-index payload.
Methods:
filtered_spec
Return an IndexSpec rebuilt from the resolved state.
bound_axes()-only reconstruction was rejected because callers need
the product name and time_unit too, not just axis objects.
Filtering as_resolved_index_record() was rejected because mixed
specs still include unbounded axes, and that path re-raises
ExtentUnknownError when it meets them.
as_resolved_index_record
Build the on-disk resolved-index record for this resolved spec.
size
Total number of slots for the given group.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
group
|
str
|
Group name. |
required |
Returns:
| Type | Description |
|---|---|
int
|
Total slot count. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If the group is not in this index. |
ExtentUnknownError
|
If the axis has no fixed extent. |
position
coordinate
as_legacy_slot_index_model
Build a SlotIndexModel for byte-parity with existing cubes.
Returns non-None ONLY when every axis is a RegularTimeAxis.
Mixed-kind specs return None; persistence for those
specs is handled by a future release.
Returns:
| Type | Description |
|---|---|
SlotIndexModel | None
|
A |
SlotIndexModel | None
|
produced, or |
firecube.ingestor.api.ResolvedIndexRecord
dataclass
On-disk record for an engine-resolved index payload.
The engine writes it to .firecube/index/current.json after the first
successful resolution. Subsequent runs read it back and verify that the
declared IndexSpec produces the same identity_hash before
writing. Inspect the record with firecube zarr index show and
regenerate it from a plugin declaration with
firecube zarr index rebuild.
Optional items carries a content-addressed manifest for
IrregularTimeAxis cubes. It is omitted from the wire format and
identity_hash for regular / integer axes so those cubes stay
byte-identical to pre-manifest records
(see compute_resolved_index_identity_hash).
Methods:
to_json_bytes
Serialise to the on-disk wire format (deterministic, UTF-8 JSON).
ASYMMETRIC: the items key is omitted when
items is None so records for regular / integer axes serialise
byte-identically to pre-manifest records.
firecube.ingestor.api.resolve_index_spec
Resolve an IndexSpec into a ResolvedIndex.
Validates that each RegularTimeAxis.coordinate matches
time_dim_name, then builds one resolver per group.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spec
|
IndexSpec
|
The index specification to resolve. |
required |
time_dim_name
|
str
|
The expected time-coordinate dimension name
(e.g. |
required |
Returns:
| Type | Description |
|---|---|
ResolvedIndex
|
A |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
If any axis coordinate does not match
|
NotImplementedError
|
If any axis kind is not supported. |
Examples:
>>> from firecube.core.api import IndexSpec, RegularTimeAxis
>>> axis = RegularTimeAxis(
... coordinate="timestamp",
... epoch="2024-01-01T00:00:00Z",
... cadence_s=600,
... slot_count=2,
... )
>>> spec = IndexSpec(name="demo", groups={"data": axis})
>>> resolved = resolve_index_spec(spec, time_dim_name="timestamp")
>>> resolved.groups
('data',)
Core API
coerce_to_epoch_s() is exported from firecube.core.api only; the ingestor
facade does not re-export it.
firecube.core.api.coerce_to_epoch_s
Coerce a coordinate value to seconds since the Unix epoch (UTC).
Accepts the following types:
str: UTC-explicit ISO 8601 string (viaiso_to_epoch_s).datetime.datetime: naive treated as UTC (FCI pattern); aware converted to UTC.numpy.datetime64: any unit, converted to seconds.pandas.Timestamp: naive treated as UTC; aware converted to UTC.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
Any
|
The coordinate value to coerce. |
required |
mode
|
str
|
|
'floor'
|
Returns:
| Type | Description |
|---|---|
int
|
Integer seconds since the Unix epoch (UTC). |
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
ValueError
|
In |