Inventory

Inventory is the top-level metadata object used by DASCore. It is the serialized manifest boundary and the object patch methods use to resolve fiber-array, acquisition, interrogator, and optical-path context. Like other objects in DASCore, it is functionally immutable.

Defining an Inventory

A minimal inventory can be created directly from DASCore’s inventory classes; in practice most users load one from YAML or a directory. See Quickstart and Simple Inventory for full walkthroughs.

import dascore as dc
from dascore.core import inventory as inv

acquisition = inv.Acquisition(
    code="RAW", location_code="01", start_time="2024-05-01",
    data_category="DAS", data_type="strain_rate", data_units="1/s",
    sample_rate=250.0, gauge_length=10.0,
    spatial_interval=1.0,
    distance_map=inv.DistanceMap(instrument_distance=(0.0,), distance=(0.0,)),
    interrogator=inv.Interrogator(model="DAS-1000"),
)
path = inv.OpticalPath(
    name="l001_path", location_code="01", start_time="2024-05-01",
    optical_components=(inv.FiberSegment(name="fiber 1", optical_length=1000.0),),
    geometry=(inv.Geometry(
        name="survey line",
        distance=(0.0, 1000.0),
        coordinates=((45.0, -112.0, 1200.0), (45.01, -112.02, 1180.0)),
    ),),
)
inventory = dc.Inventory(networks=(inv.Network(
    code="DAS",
    fiber_arrays=(inv.FiberArray(
        code="L001", name="L001", start_time="2024-05-01",
        acquisitions=(acquisition,), optical_paths=(path,),
    ),),
),))

This inventory resolves patches with acquisition_key DAS.L001.01.RAW, providing acquisition values such as sample_rate, instrument values such as interrogator.model, and optical path context such as geometry, coupling, annotations, and optical components. FiberArray identifies the durable observing system; acquisition settings and path interpretation change without changing it.

Updating an Inventory Component

Since Inventory and its components are immutable, DASCore users follow the pattern of creating a new component with updated fields, then replacing it in the inventory. The original object remains unchanged.

fiber_array = inventory.networks[0].fiber_arrays[0]
acquisition = fiber_array.acquisitions[0]

corrected_acquisition = acquisition.new(
    sample_rate=500.0,
    description="Corrected from field log.",
)

inventory_v2 = inventory.replace(acquisition, corrected_acquisition)

The same pattern applies to optical path revisions. For example, if a fiber breaks, one can discover the break by looking at continuous files, fix the end_time for the old OpticalPath, and add the new optical path as the current one.

fiber_array = inventory.networks[0].fiber_arrays[0]
opath = fiber_array.optical_paths[-1]

break_time = "2024-05-12T10:30:00.12"
break_distance = 600.0

closed_opath = opath.new(end_time=break_time)
kept, _ = opath.split_at(break_distance)
new_opath = kept.new(
    start_time=break_time,
    end_time=None,
    description="Fiber break at 600 m; sensing continues on the near side.",
)

new_fiber_array = fiber_array.new(
    optical_paths=(
        fiber_array.optical_paths[:-1] + (closed_opath, new_opath)
    )
)

inventory_v2 = inventory.replace(fiber_array, new_fiber_array)

Intervals are half-open, so break_time itself belongs to the new epoch.

A convenience function addressing the fiber array directly is proposed but not implemented; the explicit form above is how this is done today:

inventory_v2 = inventory.register_fiber_break(
    fiber_array="AA.F023", time=break_time, distance=break_distance
)

Patch Resolution

Patch enrichment starts from acquisition_key (resolution rules):

patch = patch.update_attrs(acquisition_key="DAS.L001.01.RAW")

patch = patch.enrich(
    inventory,
    attrs=("sample_rate", "gauge_length", "interrogator.model"),
    coords=("distance", "zone", "latitude", "longitude", "elevation"),
    on_missing="null",
)

Design Rule

replace is a correction (in place, retroactive); appending a closed path plus its successor is an epoch. Nothing mutates: every update builds a new inventory value.