# Timenet.dataset

## timenet.dataset

The in-memory TimeF model a connector populates during `convert()`.

### TimeAxis `module-attribute`

```
TimeAxis = RegularAxis | IrregularAxis | OrdinalAxis
```

Every shape a series' time axis can have.

### AxisType

Bases: `StrEnum`

Name which shape a series' time axis has.

TimeF stores this value and dispatches on it before it reads any shape-specific column. TimeF
never infers the case from which columns came back null.

#### IRREGULAR `class-attribute` `instance-attribute`

```
IRREGULAR = 'irregular'
```

One stored time offset per value.

#### ORDINAL `class-attribute` `instance-attribute`

```
ORDINAL = 'ordinal'
```

Order only: no cadence, no time offsets, no place on any timeline.

#### REGULAR `class-attribute` `instance-attribute`

```
REGULAR = 'regular'
```

Time offsets computed from a period and an origin.

### IrregularAxis `dataclass`

A placement no formula produces, so this axis stores every time offset.

This axis holds only the pair a builder can state and the writer can verify without a read. That
pair is the first and the last stored time offset. The time offsets themselves live in the shared
stored axis. Reach them through :attr:`~timenet.dataset.Signal.time_offsets_us`.

That split is the point. Two ints compare and hash, so the axis goes whole into the writer's series
identity and round-trips as a value through the records struct. An axis holding the array does
neither: a tuple comparison against an ndarray field raises instead of answering.

The endpoints are metadata about the stream, not an identity for it. Two series whose time offsets
differ only in the middle carry equal axes. Nothing can use axis equality to conclude the time
offsets agree. Compare the streams instead.

This axis has no `time_offset_us` and no `index_at_or_after` on purpose. Both need a read, and a
read is a series-level operation. :class:`OrdinalAxis` sets the precedent: an axis that cannot
answer in constant time does not offer the method.

#### axis\_type `class-attribute`

```
axis_type: AxisType = AxisType.IRREGULAR
```

The stored discriminator.

#### first\_us `instance-attribute`

```
first_us: int
```

Time offset of the first value, in microseconds from the record's relative zero.

#### last\_us `instance-attribute`

```
last_us: int
```

Time offset of the last value. The writer checks it against the stream at write time, so it is
verified metadata, not an unbacked claim.

#### spanning `classmethod`

```
spanning(time_offsets_us: ndarray | Sequence[int]) -> Self
```

Build the axis describing a stream of time offsets.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `time_offsets_us` | `ndarray | Sequence[int]` | The per-value time offsets, in microseconds from the record's relative zero. | *required* |

Returns:

| Type | Description |
| --- | --- |
| `Self` | The axis carrying that stream's endpoints, after :func:`to_time_offsets_us` has vetted it. |

### OrdinalAxis `dataclass`

An Axis to indicate an order without a cadence, time offsets, or place on any timeline.

#### axis\_type `class-attribute`

```
axis_type: AxisType = AxisType.ORDINAL
```

The stored discriminator.

### Record `dataclass`

Bases: `SupportsAnnotate`

One logical unit of time-series data: a recording, a session, a sensor bundle, a market window.

You create a record with :meth:`~timenet.dataset.TimeFDataset.add_record`. The record is mutable,
so you can add `annotations` after construction.

Use `metadata` for untyped JSON details. Record-level facts, such as a subject's age or the
recording device, are :class:`~timenet.types.Annotation` objects with no `span`. Such an annotation can
also state a `unit`, and it travels with every window drawn later from the record.

#### annotations `class-attribute` `instance-attribute`

```
annotations: tuple[Annotation, ...] = ()
```

Annotations attached to the record.

#### has\_absolute\_time `property`

```
has_absolute_time: bool
```

Whether this record's relative timeline has a Unix-time anchor.

#### id `property`

```
id: EntityId
```

The record's id, unique among the records of its dataset.

:meth:`~timenet.dataset.TimeFDataset.add_record` sets it, and the reader sets it on read-back.

Raises:

| Type | Description |
| --- | --- |
| `TimeFValidationError` | If the record is not in a dataset yet. |

#### metadata `class-attribute` `instance-attribute`

```
metadata: JsonMapping = field(default_factory=dict)
```

Optional JSON-compatible recording metadata.

#### signals `property`

```
signals: tuple[Signal, ...]
```

Every signal in this record's hierarchy.

#### sources `class-attribute` `instance-attribute`

```
sources: tuple[Source, ...] = ()
```

The root sources that belong to this recording, each with a distinct name.

#### start\_time `class-attribute` `instance-attribute`

```
start_time: TimeOrigin = field(default_factory=TimeOrigin)
```

The common relative zero, with an optional absolute timestamp.

#### time\_span `class-attribute` `instance-attribute`

```
time_span: TimeInterval | None = None
```

The session's overall span on the source recording timeline: an :class:`~timenet.types.TimeInterval`
covering the whole record, or `None`. Declare it when the series have gaps and an event may fall in
one, for example a note taken while every sensor was briefly off. This checks an unscoped span
against it, rather than against the union of the series' windows. Its `time_series` must be
`None`, and it must contain every series' window.

#### add\_annotations

```
add_annotations(
    annotations: Iterable[Annotation],
    *,
    warn_when_outside: bool = True,
) -> tuple[Annotation, ...]
```

Attach several annotations to the record, all together or not at all.

The whole batch is validated before any of it is attached: if one annotation fails a check, the
call raises and leaves the record unchanged. To keep the annotations before a failure attached,
loop :meth:`annotate` instead.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `annotations` | `Iterable[Annotation]` | The annotations to attach. Pass a single one to :meth:`annotate`. | *required* |
| `warn_when_outside` | `bool` | As on :meth:`annotate`. | `True` |

Returns:

| Type | Description |
| --- | --- |
| `tuple[Annotation, ...]` | New occurrences that share the input annotations' reusable content, in the order given. |

Raises:

| Type | Description |
| --- | --- |
| `TimeFValidationError` | as documented on :meth:`annotate`. |

#### annotate

```
annotate(
    annotation: Annotation,
    *,
    warn_when_outside: bool = True,
) -> Annotation
```

Attach an annotation to the record and return it.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `annotation` | `Annotation` | The annotation to attach. | *required* |
| `warn_when_outside` | `bool` | Warn and keep the span when it leaves its window, rather than raise. | `True` |

Returns:

| Type | Description |
| --- | --- |
| `Annotation` | A new occurrence that shares the input annotation's reusable content. |

Raises:

| Type | Description |
| --- | --- |
| `TimeFValidationError` | If the annotation's span references a series not on this record. If a scoped span names a timeless series. If the span falls outside the window its scope selects: the intersection of named series, the record's `time_span`, or the union of the series' windows and `warn_when_outside` is False. |

#### time\_interval

```
time_interval(
    start: datetime,
    end: datetime,
    *,
    time_series: tuple[Signal, ...] | None = None,
) -> TimeInterval
```

Build a :class:`~timenet.types.TimeInterval` between two wall-clock moments on this timeline.

The Record must have a known absolute timestamp.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `start` | `datetime` | Wall-clock start, timezone-aware. | *required* |
| `end` | `datetime` | Wall-clock end, exclusive and timezone-aware. | *required* |
| `time_series` | `tuple[Signal, ...] | None` | Series the interval is scoped to. `None` covers every series. | `None` |

Returns:

| Type | Description |
| --- | --- |
| `TimeInterval` | The half-open interval, in microseconds from this record's relative zero. |

#### time\_point

```
time_point(
    at: datetime,
    *,
    time_series: tuple[Signal, ...] | None = None,
) -> TimePoint
```

Build a :class:`~timenet.types.TimePoint` at a wall-clock moment on this record's timeline.

The Record must have a known absolute timestamp.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `at` | `datetime` | The wall-clock moment, timezone-aware. | *required* |
| `time_series` | `tuple[Signal, ...] | None` | Series the point is scoped to. `None` covers every series. | `None` |

Returns:

| Type | Description |
| --- | --- |
| `TimePoint` | The point, in microseconds from this record's relative zero. |

#### to\_arrow

```
to_arrow() -> Array
```

Read the sole signal's values as an Arrow array, for the common single-signal record.

Returns:

| Type | Description |
| --- | --- |
| `Array` | The single :class:`Signal`' values as a 1-D Arrow array. |

Raises:

| Type | Description |
| --- | --- |
| `ValueError` | If the record has more than one signal, read `signals[i]` explicitly then. |

#### to\_numpy

```
to_numpy() -> ndarray
```

Read the sole signal's values as NumPy, without building Arrow when they are NumPy.

Returns:

| Type | Description |
| --- | --- |
| `ndarray` | The single :class:`Signal`' values as an `np.ndarray`. |

Raises:

| Type | Description |
| --- | --- |
| `TimeFValidationError` | If the record does not have exactly one signal. |

#### walk\_annotations

```
walk_annotations() -> Iterable[Annotation]
```

Yield annotation occurrences from this Record, its Sources, and its Signals.

#### walk\_signals

```
walk_signals() -> Iterable[Signal]
```

Yield every signal in deterministic source and signal order.

#### walk\_sources

```
walk_sources() -> Iterable[Source]
```

Yield all sources in deterministic depth-first order.

Yields:

| Type | Description |
| --- | --- |
| `Iterable[Source]` | Every source in the record. |

Raises:

| Type | Description |
| --- | --- |
| `TimeFValidationError` | If a source is attached twice or the hierarchy contains a cycle. |

### RegularAxis `dataclass`

A constant cadence with an offset from the record's relative zero.

The origin is an index into the cadence, not a time, because a window rarely starts on a whole
microsecond. At 44.1 kHz only 3 of 1000 window starts do, so a microsecond origin is inexact for
almost every window. An index is exact for all of them.

#### axis\_type `class-attribute`

```
axis_type: AxisType = AxisType.REGULAR
```

The stored discriminator. Each shape declares its own tag, so a new shape must declare one
too.

#### offset\_us `class-attribute` `instance-attribute`

```
offset_us: int = 0
```

Whole microseconds from the record origin to the cadence's zero.

#### period\_us `instance-attribute`

```
period_us: Fraction
```

Microseconds between values. It is a :class:`~fractions.Fraction` because not every real rate is
a whole number of microseconds: 360 Hz is 25000/9 and 256 Hz is 15625/4. TimeF stores it as its
numerator and denominator.

#### start\_index `class-attribute` `instance-attribute`

```
start_index: int = 0
```

Index of this series' first value on the cadence. It is non-zero for a window cut from a longer
recording, which keeps a span written against the recording meaningful on the window.

#### at\_index

```
at_index(index: int) -> Self
```

Return the axis of a window that starts `index` values into this one.

This is exact at every rate, because the origin it moves is an index, not a derived time.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `index` | `int` | How many values into this axis the window starts. | *required* |

Returns:

| Type | Description |
| --- | --- |
| `Self` | The window's axis, which shares this period. |

#### from\_rate\_hz `classmethod`

```
from_rate_hz(
    rate_hz: int | Fraction, *, offset_us: int = 0
) -> Self
```

Build the axis of a regularly sampled series from its exact rate.

This method does not accept a float. Every real sampling rate is a whole number of values per
second, so write `500.0` as `500`. A rate that is not whole has no single reading: 29.97
fps is 2997/100 by its spelling and 30000/1001 by its intent. Those two drift 3.6 ms apart
over an hour. State which one with a :class:`~fractions.Fraction`, and build the Fraction from
a string, not a float. `Fraction(29.97)` is the binary expansion
(1054475631502295/35184372088832), and `Fraction("29.97")` is 2997/100.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `rate_hz` | `int | Fraction` | Values per second. | *required* |
| `offset_us` | `int` | Whole microseconds from the record origin to the cadence's zero. | `0` |

Returns:

| Type | Description |
| --- | --- |
| `Self` | The axis whose period is one sampling period of `rate_hz`. |

Raises:

| Type | Description |
| --- | --- |
| `TimeFValidationError` | If `rate_hz` is not a positive integer or `Fraction`. |

#### index\_at\_or\_after

```
index_at_or_after(time_offset_us: int) -> int
```

Return the first value at or after a time offset.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `time_offset_us` | `int` | The time offset, in microseconds from the record's relative zero. | *required* |

Returns:

| Type | Description |
| --- | --- |
| `int` | The index within this series, which is negative if the time offset precedes its first value. |

#### time\_offset\_us

```
time_offset_us(index: int) -> int
```

Return the time offset of one value, floored to whole microseconds.

This floor pairs with the ceiling in :meth:`index_at_or_after`. The two are exactly inverse at
every period of one microsecond or coarser, including the non-integral ones.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `index` | `int` | The value's index within this series. | *required* |

Returns:

| Type | Description |
| --- | --- |
| `int` | Microseconds from the record's relative zero. |

### Signal `dataclass`

Bases: `SupportsAnnotate`

Reference to one logical stream of time-series data, with optional windowing and lazy values.

Each Signal has exactly one owning Source. Several Signals can share an immutable TimeAxis or
TimeSeriesSpec. Consumers read values through :meth:`to_arrow` or :meth:`to_numpy`, which apply the
spec. The values sit behind an internal :class:`~timenet.types.ArrayHandle`: the constructor holds
them in memory, and :meth:`from_loader` takes a handle or a callable.

#### annotations `class-attribute` `instance-attribute`

```
annotations: tuple[Annotation, ...] = ()
```

Annotations attached directly to this signal.

#### id `property`

```
id: EntityId
```

The signal's id, unique among the signals of its dataset.

:meth:`~timenet.dataset.TimeFDataset.add_record` sets it, and the reader sets it on read-back.

Raises:

| Type | Description |
| --- | --- |
| `TimeFValidationError` | If the signal's record is not in a dataset yet. |

#### is\_ordinal `property`

```
is_ordinal: bool
```

Whether this series counts steps only and has no timeline, so time spans cannot place it.

#### metadata `class-attribute` `instance-attribute`

```
metadata: JsonMapping = field(default_factory=dict)
```

Optional JSON-compatible signal metadata.

#### n\_values `instance-attribute`

```
n_values: int
```

The number of values the series holds, one per timestep. If `spec` gives each timestep a shape,
the series counts one value per timestep, not one per scalar. This matches the count a chunk's
`n_values` reports.

#### name `instance-attribute`

```
name: str
```

Human-readable signal name within the source.

#### null\_count `instance-attribute`

```
null_count: int | None
```

The number of missing timesteps, or `None` if unknown.

Signals built from values in memory have a known count. Signals built with :meth:`from_loader`
have a count only if `null_count` is supplied. Slices have an unknown count.
The writer counts missing timesteps and rejects a supplied count that differs from the values.

#### span\_us `property`

```
span_us: tuple[int, int] | None
```

The half-open microsecond window this series covers, or `None` if it has no timeline.

The axis derives this window, so the window and the axis cannot disagree. The dispatch ends in
:func:`~typing.assert_never`, so a new axis shape breaks this method at type-check time.

Returns:

| Type | Description |
| --- | --- |
| `tuple[int, int] | None` | `(first time_offset, one past the last)` in microseconds, or `None` for an ordinal series. |

#### spec `instance-attribute`

```
spec: TimeSeriesSpec
```

Measurement-modality contract: type tag, units, dtype, and per-timestep shape.

#### time\_axis `instance-attribute`

```
time_axis: TimeAxis
```

Where this series' values sit in time. A :class:`~timenet.dataset.axis.RegularAxis` gives a
cadence, an :class:`~timenet.dataset.axis.IrregularAxis` stores per-value time offsets, and an
:class:`~timenet.dataset.axis.OrdinalAxis` marks a sequence with no time at all.

#### annotate

```
annotate(annotation: Annotation) -> Annotation
```

Attach one annotation and return it.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `annotation` | `Annotation` | The annotation to attach. | *required* |

Returns:

| Type | Description |
| --- | --- |
| `Annotation` | The attached annotation. |

#### from\_irregular `classmethod`

```
from_irregular(
    values: ndarray
    | Sequence[bool | int | float | str | None]
    | Array,
    *,
    time_offsets_us: ndarray | Sequence[int],
    spec: TimeSeriesSpec,
    name: str,
    full_validation: bool = True,
) -> Signal
```

Build an irregular series from materialized values and their time offsets.

The axis endpoints come from the stream itself, so the two cannot disagree. You cannot state a
first or last time offset that the time offsets do not have. To convert wall-clock moments, use
:func:`~timenet.dataset.axis.time_offsets_from_datetimes` before you call.
Numeric NumPy values and normalized time offsets retain their buffers for NumPy reads, which
return read-only arrays. Arrow conversion happens only on an Arrow read. Treat retained input
buffers as read-only.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `values` | `ndarray | Sequence[bool | int | float | str | None] | Array` | Values to convert to the specified dtype. Text and enum values are strings. `None` marks a missing timestep. | *required* |
| `time_offsets_us` | `ndarray | Sequence[int]` | One time offset per value, in microseconds from the record's relative zero. | *required* |
| `spec` | `TimeSeriesSpec` | The specification for the signal's values. | *required* |
| `name` | `str` | The signal name. | *required* |
| `full_validation` | `bool` | Check timestamp ordering by default. Set to `False` only for previously validated timestamps to skip that scan. Both modes check type, shape, integer range, endpoints, and one timestamp per value. Conversion can still allocate when needed. | `True` |

Returns:

| Type | Description |
| --- | --- |
| `Signal` | The constructed :class:`Signal`. |

Raises:

| Type | Description |
| --- | --- |
| `TimeFValidationError` | If the time offsets are unusable, or there is not exactly one per value. |

#### from\_loader `classmethod`

```
from_loader(
    *,
    spec: TimeSeriesSpec,
    time_axis: TimeAxis,
    name: str,
    loader: ArrayHandle | Callable[[], Array],
    n_values: int,
    null_count: int | None = None,
    time_offsets_loader: ArrayHandle
    | Callable[[], Array]
    | None = None,
    annotations: tuple[Annotation, ...] = (),
    metadata: JsonMapping | None = None,
) -> Signal
```

Build a signal whose values are loaded only when they are read.

`loader` and `time_offsets_loader` each accept an `ArrayHandle` or a callable that returns
an Arrow array. The signal keeps handles unchanged and calls plain callables on every read.
A callable with a `read_steps()` method must subclass `ArrayHandle`.
`null_count` gives the number of missing timesteps, or `None` if unknown.
The writer counts missing timesteps when it reads the values.

Returns:

| Type | Description |
| --- | --- |
| `Signal` | A signal backed by `loader` without reading it. |

#### from\_values `classmethod`

```
from_values(
    values: ndarray
    | Sequence[bool | int | float | str | None],
    *,
    spec: TimeSeriesSpec,
    name: str,
    time_axis: TimeAxis,
) -> Signal
```

Build a series from already-materialized values and hold them in memory in the spec's dtype.

Numeric NumPy arrays retain their buffers when dtype and layout already match the spec.
NumPy reads return fresh read-only views; treat the input buffers as read-only while in use.
Arrow conversion happens only on an Arrow read. Other inputs become Arrow arrays immediately.
The array length sets `n_values`.

For files or remote sources, use :meth:`from_loader` and supply the length. That constructor
does not read the values immediately.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `values` | `ndarray | Sequence[bool | int | float | str | None]` | Values to convert to the specified dtype. Text and enum values are strings. `None` marks a missing timestep. | *required* |
| `spec` | `TimeSeriesSpec` | The specification for the signal's values. | *required* |
| `name` | `str` | The signal name. | *required* |
| `time_axis` | `TimeAxis` | Where the values sit in time. | *required* |

Returns:

| Type | Description |
| --- | --- |
| `Signal` | The constructed :class:`Signal`. |

#### is\_selected\_by

```
is_selected_by(span: Span) -> bool
```

Return whether `span` applies to this series.

A step span selects only the series it counts on. A time span with no `time_series`
selects every series, and a time span with `time_series` set selects only the series it
lists. Series are compared by identity, not by name, so a span does not select a slice of a
series it names: the slice is a new series.

This checks the scope only, not the time.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `span` | `Span` | A time or step span, point or interval. | *required* |

Returns:

| Type | Description |
| --- | --- |
| `bool` | `True` if the span applies to this series. |

Raises:

| Type | Description |
| --- | --- |
| `TimeFValidationError` | If `span` is neither a time span nor a step span. |

#### overlaps\_with

```
overlaps_with(span: TimeSpan) -> bool
```

Return whether at least one value of this series falls inside `span`.

A value is inside when its time offset falls in `[start_us, exclusive_end)`, the rule
:meth:`slice` uses, so for a time interval `slice(span, clip=True)` keeps a value whenever
this returns `True`. This compares times only and ignores the span's `time_series`: ask
:meth:`is_selected_by` whether the span applies to this series.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `span` | `TimeSpan` | A time interval or time point on the record timeline. | *required* |

Returns:

| Type | Description |
| --- | --- |
| `bool` | `True` if a value of this series falls inside the span. |

Raises:

| Type | Description |
| --- | --- |
| `TimeFValidationError` | If `span` is not a :class:`~timenet.types.TimeSpan`. If the series is ordinal: it has no timeline, so check :attr:`is_ordinal` first. |

#### read\_steps

```
read_steps(start: int, stop: int) -> Array
```

Read a half-open temporal step range as Arrow without forcing a NumPy conversion.

A series whose stored values were loaded, or that holds its values in memory, is sliced. An
unloaded stored series reads only the chunks that intersect the range, and one from a loader
callable is read in full first.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `start` | `int` | First temporal step, inclusive. | *required* |
| `stop` | `int` | Last temporal step, exclusive. | *required* |

Returns:

| Type | Description |
| --- | --- |
| `Array` | A primitive Arrow array for scalar series or a fixed-shape tensor array for N-D series. |

Raises:

| Type | Description |
| --- | --- |
| `TimeFValidationError` | If the range is negative or reversed. |

#### slice

```
slice(span: Span, *, clip: bool = False) -> Signal
```

Return a new lazy series that holds only the steps `span` covers.

The span must hold at least one value. A step span must count on this series. A time span is
placed by time only, so ask :meth:`is_selected_by` first when the scope matters. The slice
keeps the name, spec, metadata, and place on the timeline, drops annotations, and reads no
values until it is read. The slice has `null_count=None` because its number of missing
timesteps is unknown::

```
if signal.overlaps_with(interval):
    part = signal.slice(interval, clip=True)
```

The "Cut a window" docs show how to cut every signal of a record.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `span` | `Span` | The interval to keep: a time interval within :attr:`span_us`, or a step interval on this series that ends at or before :attr:`n_values`. | *required* |
| `clip` | `bool` | Keep the part of the span inside this series instead of raising when the span reaches past it. | `False` |

Returns:

| Type | Description |
| --- | --- |
| `Signal` | A lazy :class:`Signal` that holds the steps the span covers. |

Raises:

| Type | Description |
| --- | --- |
| `TimeFValidationError` | If `span` is a point, a step span on another series, or a time span on an ordinal series. If the span reaches past this series and `clip` is false, or covers no step of this series. |

#### step\_range

```
step_range(span: Span) -> tuple[int, int]
```

Return the half-open step range `(start, stop)` of this series that `span` covers.

The bridge to step-based forecasting libraries: `stop - start` is the horizon `h` that
GluonTS, Nixtla, and fev speak in. The pair feeds :meth:`read_steps` to read the ground
truth. A step span already counts in this series' own steps, so it is the range, bounded by the
series' length. The axis locates a time span instead: the steps whose time offsets fall in
`[start_us, end_us)`, each rounded up to the next step. An ordinal series has no timeline, so a
time span has no answer on it.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `span` | `Span` | The interval to locate. A step span must name this series. | *required* |

Returns:

| Type | Description |
| --- | --- |
| `tuple[int, int]` | `(start, stop)` step indices, half-open, from this series' first step. |

Raises:

| Type | Description |
| --- | --- |
| `TimeFValidationError` | If `span` is a point. If a step span does not name this series or runs past its length. If an ordinal series receives a time span, or the span resolves past the series' steps. If the located range is empty. |

#### time\_offsets\_us

```
time_offsets_us() -> ndarray
```

Read this series' per-value time offsets.

Only an irregular series has this. A regular axis computes its time offsets without a read, and an
ordinal one has none. So neither has a stream to return.

Returns:

| Type | Description |
| --- | --- |
| `ndarray` | One int64 microsecond time offset per value. |

Raises:

| Type | Description |
| --- | --- |
| `TimeFValidationError` | If this series' axis is not an :class:`~timenet.dataset.axis.IrregularAxis`. |

#### to\_arrow

```
to_arrow() -> Array
```

Read the series' values as an Arrow array.

Returns:

| Type | Description |
| --- | --- |
| `Array` | A primitive array for scalar values or a fixed-shape tensor array for N-D values. |

#### to\_numpy

```
to_numpy() -> Shaped[ndarray, ' time *value']
```

Read the series' values as a read-only NumPy array.

Returns:

| Type | Description |
| --- | --- |
| `Shaped[ndarray, ' time *value']` | The series' values with shape `(n_steps, *spec.value_shape)`. |

Raises:

| Type | Description |
| --- | --- |
| `TimeFNullValuesError` | If a timestep is wholly or partly missing. Use :meth:`to_arrow` to preserve missing values. |

### SignalSelection `dataclass`

A validated set of signals selected from one source subtree.

#### signals `instance-attribute`

```
signals: tuple[Signal, ...]
```

Selected descendant signals in caller order.

#### source `instance-attribute`

```
source: Source
```

Source whose descendant Signals can be selected.

#### annotate

```
annotate(annotation: Annotation) -> tuple[Annotation, ...]
```

Attach one annotation content item to the selected signals.

Timed and static annotations create one occurrence per selected Signal.
Each occurrence shares the original content.
All selected Signals are validated before any annotation is attached.

Returns:

| Type | Description |
| --- | --- |
| `tuple[Annotation, ...]` | The attached occurrences in selection order. |

#### from\_source `classmethod`

```
from_source(
    source: Source,
    *,
    signals: tuple[Signal, ...] | None,
    signal_names: tuple[str, ...] | None,
) -> SignalSelection
```

Resolve an object or name selection against a source subtree.

Returns:

| Type | Description |
| --- | --- |
| `SignalSelection` | The validated selection. |

Raises:

| Type | Description |
| --- | --- |
| `TimeFValidationError` | If selectors are missing, mixed, duplicated, ambiguous, or unknown. |

### Source `dataclass`

Bases: `SupportsAnnotate`

A device, sensor, or subsystem that contains child sources and signals.

#### annotations `class-attribute` `instance-attribute`

```
annotations: tuple[Annotation, ...] = ()
```

Annotations attached to this source.

#### metadata `class-attribute` `instance-attribute`

```
metadata: JsonMapping = field(default_factory=dict)
```

Optional JSON-compatible source metadata.

#### name `instance-attribute`

```
name: str
```

Human-readable source name.

#### signals `class-attribute` `instance-attribute`

```
signals: tuple[Signal, ...] = ()
```

Signals produced directly by this source.

#### sources `class-attribute` `instance-attribute`

```
sources: tuple[Source, ...] = ()
```

Direct child sources, each with a distinct name. Their order has no semantic meaning.

#### annotate

```
annotate(annotation: Annotation) -> Annotation
```

Attach one annotation and return it.

Returns:

| Type | Description |
| --- | --- |
| `Annotation` | The attached annotation. |

#### select

```
select(
    *,
    signals: tuple[Signal, ...] | None = None,
    signal_names: tuple[str, ...] | None = None,
) -> SignalSelection
```

Select descendant signals by object or by human-readable name.

Returns:

| Type | Description |
| --- | --- |
| `SignalSelection` | A selection that can be annotated as one declarative operation. |

#### walk\_signals

```
walk_signals() -> Iterator[Signal]
```

Yield every descendant signal in deterministic source and signal order.

Yields:

| Type | Description |
| --- | --- |
| `Signal` | Signals from this source and every descendant source. |

#### walk\_sources

```
walk_sources() -> Iterator[Source]
```

Yield this source and all descendants in deterministic depth-first order.

Yields:

| Type | Description |
| --- | --- |
| `Source` | This source followed by its descendants, sorted by name at each level. |

Raises:

| Type | Description |
| --- | --- |
| `TimeFValidationError` | If the graph contains a cycle or one source has two parents. |

### TimeFDataset

Bases: `SupportsAnnotate`

Holds records and their tasks as Python objects. It does not do I/O. The writer handles persistence.

#### annotations `property`

```
annotations: tuple[Annotation, ...]
```

Annotations attached to the dataset itself.

#### has\_task\_stream `property`

```
has_task_stream: bool
```

Whether tasks stream from a source (see :meth:`set_task_stream`) rather than the `_tasks` list.

#### last\_ids `property`

```
last_ids: dict[ObjectKind, EntityId]
```

The last id used per kind. A written version stores it, so later edits never reuse an id.

#### lineage `property`

```
lineage: Mapping[str, ParentDatasetCard]
```

The card of every ancestor release, parents included, keyed by `org/name@version`.

The mapping is read-only. :meth:`import_from` adds to it.

#### metadata `property`

```
metadata: DatasetMetadata
```

The dataset's descriptive identity.

#### parent\_cards `property`

```
parent_cards: Mapping[ParentAlias, ParentDatasetCard]
```

A copy of the card of each parent this dataset imports from, by parent alias.

The mapping is read-only. :meth:`import_from` adds to it.

#### records `property`

```
records: tuple[Record, ...]
```

All records of the dataset.

For a dataset read from a release, the records come in the release's record order, as
:meth:`~timenet.reader.TimeFReader.record_ids` lists it. Records added later follow them.
For a dataset built in memory, the records come in the order in which they were added.

#### schema `property`

```
schema: DatasetSchema | None
```

The derived schema, or `None` until :meth:`derive_schema` is called.

#### tasks `property`

```
tasks: tuple[Task, ...]
```

All tasks in insertion order.

#### add\_record

```
add_record(*, record: Record) -> Record
```

Register and return a complete record.

The record and each of its signals get the next id of their kind.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `record` | `Record` | The complete record hierarchy to register. | *required* |

Returns:

| Type | Description |
| --- | --- |
| `Record` | The same :class:`Record` instance, with its id and its signals' ids set. |

#### add\_task

```
add_task(*, task: Task) -> Task
```

Validate and register one fully constructed task.

The Task owns its input and target object references. Registration verifies those objects
against this dataset before it changes the dataset. It does not change the input Records.
The task gets the next task id.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `task` | `Task` | The concrete task to register. | *required* |

Returns:

| Type | Description |
| --- | --- |
| `Task` | The same task instance. |

#### add\_tasks

```
add_tasks(*, tasks: Iterable[Task]) -> tuple[Task, ...]
```

Register several complete tasks, all together or not at all.

The method validates the whole batch before it attaches any task. If one task fails a
check, the call raises an error, and the dataset and every task in the batch stay
unchanged. To keep the tasks that passed before a failure, call :meth:`add_task` in a
loop instead.

A task can derive from another task in the same batch. To do this, list the parent task
in the deriving task's `from_tasks` field. The batch is checked as a unit, so the order
of tasks within `tasks` does not matter.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `tasks` | `Iterable[Task]` | The task instances to register. For a single task, use :meth:`add_task` instead. | *required* |

Returns:

| Type | Description |
| --- | --- |
| `tuple[Task, ...]` | The registered tasks, in the order given. |

#### annotate

```
annotate(annotation: Annotation) -> Annotation
```

Attach a static annotation to the dataset.

Returns:

| Type | Description |
| --- | --- |
| `Annotation` | The attached occurrence. |

Raises:

| Type | Description |
| --- | --- |
| `TimeFValidationError` | If the annotation has a time placement, which is ambiguous across records. |

#### derive\_schema

```
derive_schema() -> DatasetSchema
```

Walk the dataset's instances and build its :class:`DatasetSchema`.

This method collects the distinct spec, annotation, and task types. It stores the result
on the dataset, and it returns the result.

Returns:

| Type | Description |
| --- | --- |
| `DatasetSchema` | The derived :class:`DatasetSchema`. |

Raises:

| Type | Description |
| --- | --- |
| `TimeFValidationError` | If one spec type or annotation key yields conflicting descriptors across records. |

#### describe

```
describe(
    *, rows: int = 5, file: TextIO | None = None
) -> None
```

Print a plain-text summary of the dataset: its identity, counts, specs and columns, and a record preview.

This method works like pandas' `describe` and `info` methods. The preview reads only
span metadata, not series values. The method checks value dtypes from one series per
spec. This method works even before :meth:`derive_schema` runs, because it computes
everything from the records.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `rows` | `int` | The number of records to show in the preview. | `5` |
| `file` | `TextIO | None` | Where to write the output. The default is `sys.stdout`. | `None` |

#### from\_parts `classmethod`

```
from_parts(
    *,
    metadata: DatasetMetadata,
    records: Iterable[Record],
    tasks: Iterable[Task],
    schema: DatasetSchema,
    annotations: Iterable[Annotation] = (),
    last_ids: Mapping[ObjectKind, EntityId] | None = None,
    import_state: ImportState | None = None,
) -> TimeFDataset
```

Build a dataset from parts that are already constructed.

The reader uses this method when it reads a dataset back from disk.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `metadata` | `DatasetMetadata` | The dataset's descriptive identity. | *required* |
| `records` | `Iterable[Record]` | Fully built records. Their loaders pull data from disk. | *required* |
| `tasks` | `Iterable[Task]` | Fully built tasks, with their `from_tasks` references resolved. | *required* |
| `schema` | `DatasetSchema` | The schema reconstructed from the manifest. | *required* |
| `annotations` | `Iterable[Annotation]` | Annotations attached directly to the dataset. | `()` |
| `last_ids` | `Mapping[ObjectKind, EntityId] | None` | The id counters of the dataset these parts come from, so new objects never reuse the id of a removed one. | `None` |
| `import_state` | `ImportState | None` | The parent cards, the lineage, and the import map of the dataset these parts come from. The new dataset keeps this object. Without it, the dataset starts with no parent cards, no lineage, and an empty import map. | `None` |

Returns:

| Type | Description |
| --- | --- |
| `TimeFDataset` | The dataset, built from these parts. The records, signals, and tasks keep the ids they |
| `TimeFDataset` | have. |

#### get\_all

```
get_all() -> tuple[Task, ...]
```

Return every task whatever its split, in insertion order.

Returns:

| Type | Description |
| --- | --- |
| `tuple[Task, ...]` | The tasks. A streamed dataset drains its stream and materializes them. |

#### get\_split

```
get_split(split: Split | str) -> tuple[Task, ...]
```

Return the tasks of one split, in insertion order.

A dataset partitions its tasks by setting :attr:`~timenet.types.Task.split` on each one.
Records carry no split of their own: a record may serve tasks of several splits. A streamed
dataset drains its stream and materializes the selection.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `split` | `Split | str` | The partition to select, as a :class:`~timenet.types.Split` or its value. | *required* |

Returns:

| Type | Description |
| --- | --- |
| `tuple[Task, ...]` | The matching tasks. Empty when the dataset partitions its tasks but none is in `split`. |

Raises:

| Type | Description |
| --- | --- |
| `TimeFValidationError` | If `split` is unknown, or if no task of the dataset belongs to any split. Asking a partition of a dataset that has none is a mistake rather than an empty selection, so it fails rather than returning nothing. |

#### get\_splits

```
get_splits(*splits: Split | str) -> tuple[Task, ...]
```

Return the union of selected splits without changing task assignments.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `*splits` | `Split | str` | One or more partitions, such as `Split.TRAIN, Split.VALIDATION`. | `()` |

Returns:

| Type | Description |
| --- | --- |
| `Task` | Matching tasks in dataset order. Repeated split names do not duplicate tasks. |
| `...` | Tasks without a split are excluded. A streamed dataset is read once to materialize |
| `tuple[Task, ...]` | the selection. |

Raises:

| Type | Description |
| --- | --- |
| `TimeFValidationError` | If no splits are supplied, a name is unknown, or the dataset assigns no split to any task. A missing partition otherwise returns no tasks. |

#### get\_test

```
get_test() -> tuple[Task, ...]
```

Return the tasks of the test split. See :meth:`get_split`.

#### get\_train

```
get_train() -> tuple[Task, ...]
```

Return the tasks of the training split. See :meth:`get_split`.

#### get\_validation

```
get_validation() -> tuple[Task, ...]
```

Return the tasks of the validation split. See :meth:`get_split`.

#### import\_from

```
import_from(
    parent: TimeFReader,
    *,
    alias: ParentAlias | None = None,
    records: Iterable[EntityId] = (),
    signals: Iterable[EntityId] = (),
    tasks: Iterable[EntityId] = (),
    load: LoadPlan = ALL_LAZY,
) -> ImportedDataset
```

Copy Records, Signals, and tasks from a parent release into this dataset.

The parent must be a release that `metadata.parents` declares. The call records the parent
in :attr:`parent_cards` and :attr:`lineage`. It does not change the card of this dataset.

* `records=` copies each Record whole, with its Sources, Signals, and annotations.
* `signals=` copies each Signal into a copy of its Record and of the Sources above it. Such a
  partial copy keeps the annotations that its Signals place.
* `tasks=` copies each task with the Records and Signals that it references and the tasks
  that it derives from. Each annotation that a task references points at the matching
  occurrence on the copy of its owner. If this dataset changed or removed that occurrence,
  the import attaches a copy of the parent's next to it.

Each copy is this dataset's own object, with new IDs and its own metadata and annotations, so
edits do not change the parent. Its Signals keep the parent's value handles, so they read the
parent's stored values unless `load` loads them. A write of this dataset reuses the parent's
payloads, so it needs the parent release. Annotations on the parent dataset itself come only
with a task that references them.

An import of an object whose copy this dataset holds reuses that copy, with this dataset's
edits. If the call needs Signals that the copy of a Record lacks, it adds them to that copy,
with the Sources and annotations that they bring. An object whose copy this dataset removed is
copied again, under a new ID. An edit such as :func:`~timenet.dataset.edit.remove_signals`
keeps the import state in the dataset it builds, so an import into that dataset works the
same way. Imports are safe to repeat: if a call fails partway, repeating it completes it.
Select many objects in one call, because each call reads them in batches.
`docs/timef-dataset.md` describes the rules.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `parent` | `TimeFReader` | A reader of the parent release. | *required* |
| `alias` | `ParentAlias | None` | The parent alias that declares the parent. By default, the alias that the card declares for its release. | `None` |
| `records` | `Iterable[EntityId]` | The parent IDs of the Records to copy whole. | `()` |
| `signals` | `Iterable[EntityId]` | The parent IDs of the Signals to copy. | `()` |
| `tasks` | `Iterable[EntityId]` | The parent IDs of the tasks to copy. | `()` |
| `load` | `LoadPlan` | Which kinds of stored values to load. The import reads the parent tasks and Records without their values. Then it loads these kinds on the copies of the selected and needed tasks, Records, and Signals, and on the annotations that it copies, through their own value handles. This includes copies that this dataset holds already. The other Signals of a partial copy stay as they are. The default loads none. | `ALL_LAZY` |

Returns:

| Type | Description |
| --- | --- |
| `ImportedDataset` | The copies of the selected Records, Signals, and tasks, by their parent IDs. |

Raises:

| Type | Description |
| --- | --- |
| `TimeFValidationError` | If `metadata.parents` or `alias` does not declare the parent, another alias imports the parent or this alias imports another release, a selection is not a collection of positive int64 IDs, the copy of a Record that the call adds to holds sibling Sources of one name or a Signal outside its `time_span`, `tasks` is given and this dataset streams its tasks, a task to copy is a `TSCorrespondenceTask` without `candidate_records`, or the task copies fail to register. |
| `TimeFNotFoundError` | If a selected ID names no parent object. This dataset does not change. |

#### iter\_streamed\_tasks\_validated

```
iter_streamed_tasks_validated() -> Iterator[Task]
```

Yield the streamed tasks, validating each against the dataset before it is written.

The id of a streamed task is the task id counter's value at :meth:`set_task_stream` plus
its position in the stream. Thus every pass over the stream gives the task the same id.

Streamed tasks skip :meth:`add_task`'s checks, so validate each here as it passes through: an
undeclared type, an attachment to an unknown record, a dangling reference, a bad answer, or an
out-of-window span raises before the task reaches disk. The dataset holds no task list. Thus
the cross-task checks (`from_tasks` derivations) that need every task at once do not run
for a stream.

Yields:

| Type | Description |
| --- | --- |
| `Task` | Each validated task, in the source's order. |

Raises:

| Type | Description |
| --- | --- |
| `TimeFValidationError` | If a streamed task fails one of the per-task checks. |

#### iter\_tasks

```
iter_tasks(
    *,
    required_modalities: Iterable[InputModality]
    | None = None,
    supported_modalities: Iterable[InputModality]
    | None = None,
    split: Split
    | str
    | Iterable[Split | str]
    | None = None,
) -> Iterator[Task]
```

Yield the dataset's tasks, from the stream when one is set, else the materialized list.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `split` | `Split | str | Iterable[Split | str] | None` | One split or an iterable of splits to select, or `None` for every task. Selection preserves dataset order and original task assignments. | `None` |

Yields:

| Type | Description |
| --- | --- |
| `Task` | Each task. A streamed dataset re-reads its source on every call. |

Raises:

| Type | Description |
| --- | --- |
| `TimeFValidationError` | If the selection is empty or contains an unknown split. |

#### set\_task\_stream

```
set_task_stream(
    task_types: Sequence[type[Task]],
    source: Callable[[], Iterator[Task]],
) -> None
```

Provide tasks as a re-iterable stream instead of materializing them in the dataset.

For a dataset with far more tasks than records (many questions over few recordings), holding
every task in memory does not scale. A streaming connector builds the bounded records. Then it
gives the tasks through `source`. The writer streams them to disk without a list.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `task_types` | `Sequence[type[Task]]` | The task classes the stream yields, so :meth:`derive_schema` records them. Every yielded task must be one of these types. | *required* |
| `source` | `Callable[[], Iterator[Task]]` | A callable returning a fresh iterator over the tasks each time it is called. The writer calls it more than once (a peek for id storage, then the write), so it must re-read its source rather than exhaust a one-shot generator. | *required* |

Raises:

| Type | Description |
| --- | --- |
| `TimeFValidationError` | If tasks were already added with :meth:`add_task`. A dataset either streams its tasks or materializes them, never both, or the writer would drop one set. |

#### tasks\_for

```
tasks_for(record: Record) -> tuple[Task, ...]
```

```
tasks_for(
    record: Record, task_type: type[TTask]
) -> tuple[TTask, ...]
```

```
tasks_for(
    record: Record, task_type: type[Task] = Task
) -> tuple[Task, ...]
```

Return the tasks that take a record as input, optionally filtered by type.

Tasks reference their records, so this method scans the tasks for the ones whose inputs
hold this record.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `record` | `Record` | The record whose tasks to find. | *required* |
| `task_type` | `type[Task]` | Keep only tasks of this subclass. The default keeps every task on the record. | `Task` |

Returns:

| Type | Description |
| --- | --- |
| `tuple[Task, ...]` | The record's tasks of `task_type`, in insertion order. |

Raises:

| Type | Description |
| --- | --- |
| `TimeFValidationError` | If `record` is not registered in this dataset. |

#### tasks\_of

```
tasks_of(task_type: type[TTask]) -> tuple[TTask, ...]
```

Return every task of a given type, in insertion order.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `task_type` | `type[TTask]` | The task subclass to keep, for example :class:`~timenet.types.ClassificationTask`. | *required* |

Returns:

| Type | Description |
| --- | --- |
| `tuple[TTask, ...]` | The matching tasks. |

#### to\_features\_and\_targets

```
to_features_and_targets(
    *,
    task: type[Task] | None = ...,
    output: Literal["arrow"] = ...,
    features: Literal["timestep", "series"] = ...,
    split: Split | str | None = ...,
) -> tuple[Array, Array]
```

```
to_features_and_targets(
    *,
    task: type[Task] | None = ...,
    output: Literal["numpy"],
    features: Literal["timestep", "series"] = ...,
    split: Split | str | None = ...,
) -> tuple[ndarray, ndarray]
```

```
to_features_and_targets(
    *,
    task: type[Task] | None = None,
    output: Literal["arrow", "numpy"] = "arrow",
    features: Literal["timestep", "series"] = "timestep",
    split: Split | str | None = None,
) -> tuple[Array, Array] | tuple[ndarray, ndarray]
```

Build an `(X, y)` training pair. By default, this method defers materialization.

This method requires every record to carry exactly one task of `task`. It pairs the
values of that task's sole signal with the task's only scalar target. With `split`, only
the tasks of that split count, and a record that has none of them is left out rather than
rejected, so one call builds `(X_train, y_train)` and another `(X_test, y_test)`. The
`features` argument chooses the shape of `X`:

* `"timestep"` (the default): one feature per point, in a rectangular matrix. This needs
  equal-length records. The result is an Arrow `FixedSizeListArray[T]`, or a NumPy
  `(n, T)` array of `float32` values.
* `"series"`: one sequence feature per record, so variable-length series work too. The
  result is an Arrow `ListArray`, or a NumPy `(n,)` object array of 1-D arrays.

With `output="arrow"` (the default), the method builds these arrays straight from the
series loaders, with no NumPy copy in between. With `output="numpy"`, the method
materializes them. `y` is always the targets, as an Arrow string array or a 1-D NumPy
array.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `task` | `type[Task] | None` | The task type to read targets from, for example :class:`~timenet.types.ClassificationTask`. Omit this argument to infer the type when the dataset has exactly one task type that carries an inline target. | `None` |
| `output` | `Literal['arrow', 'numpy']` | Use `"arrow"` to keep the deferred Arrow arrays, or `"numpy"` to materialize them. | `'arrow'` |
| `features` | `Literal['timestep', 'series']` | Use `"timestep"` for a rectangular per-point matrix, or `"series"` for one variable-length sequence per record. | `'timestep'` |
| `split` | `Split | str | None` | Keep only the tasks of this split, or `None` for every task. | `None` |

Returns:

| Type | Description |
| --- | --- |
| `tuple[Array, Array] | tuple[ndarray, ndarray]` | `(X, y)` as two Arrow arrays when `output="arrow"`, or two NumPy arrays when |
| `tuple[Array, Array] | tuple[ndarray, ndarray]` | `output="numpy"`. |

Raises:

| Type | Description |
| --- | --- |
| `TimeFValidationError` | This error occurs if `output` or `features` is invalid. It also occurs if `task` is omitted and the dataset has zero or several task types with inline targets. It also occurs if a matched task carries no inline target, for example if its answer is a produced series or is stored as target annotations. It also occurs if a matched record is not single signal, or if `features="timestep"` is asked of records that are not all the same length. It also occurs if the dataset has no records, or if any record does not carry exactly one task of `task`. |

#### write

```
write(
    *,
    path: str | Path,
    values_backend: ValuesBackend = PARQUET,
) -> Path
```

Write this dataset as one immutable TimeF version.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `path` | `str | Path` | Registry root under which the dataset ID and version directories are created. | *required* |
| `values_backend` | `ValuesBackend` | Typed values-plane backend selection. | `PARQUET` |

Returns:

| Type | Description |
| --- | --- |
| `Path` | The committed version directory. |
