Skip to content

description: The chiptime object model: ParseResult, Activity, Session, Records, Streams — the object tree and the ideas that organize it.

Python API — model

What parse returns and what you drill into. Everything on this page is plain data — frozen or simple dataclasses with no hidden state, no lazy IO, no side effects. You make one call, then navigate attributes.

The object tree

ParseResult                         one parse() call returns exactly this
├─ ok · mode · file_type · source        the verdict + input identity
├─ errors · warnings · provenance        the paper trail (coded diagnostics)
├─ recovery                              what salvage did (None if unneeded)
├─ messages → [Message]                  lossless middle layer, file order
└─ activity → Activity                   the workout, made sense of
    ├─ sessions → [Session]              one per sport bout
    │   ├─ declared / derived → Totals   device's claim vs recomputed truth
    │   ├─ discrepancies                 where those two disagree
    │   ├─ laps · lengths                declared structure
    │   └─ records → Records             the per-second timeline
    │       └─ streams → {Stream}        columnar; null ≠ 0, always
    ├─ gaps                              recording holes, classified
    └─ events · device · athlete · hrv_intervals_s

Three ideas organize it:

  1. Two truths, kept side by side. Devices declare totals; chiptime recomputes them from the records. Session.declared and Session.derived are both Totals, and discrepancies lists every material disagreement — the disagreement is signal, not noise to reconcile away.
  2. Columns, not rows. Records stores one shared time axis plus one Stream per field. That makes analytics natural (a stream is already a series) and keeps unknown fields lossless — every field any record carried becomes a stream, known or not.
  3. The paper trail is part of the result. provenance on ParseResult is the complete list of decisions chiptime made about your data. An empty list means the file was exactly what it claimed to be.

The result envelope

chiptime.result.ParseResult

Everything chiptime.parse learned about one source.

The navigation model: one call, then drill into plain data — result.activity.sessions[0].records.stream("power"). Nothing here is lazy or stateful; what you see is the complete, final read.

The paper trail is the other half: errors (what was wrong), warnings (what was suspicious), and provenance (every drop, repair, and reinterpretation chiptime performed). An empty paper trail means the file was exactly what it claimed to be.

Attributes:

Name Type Description
ok

True when usable content was produced.

mode Mode

The policy used (strict | lenient | forensic).

source

Input identity (SourceInfo).

parts

One FitPart per FIT file found in the source.

errors

Structural problems, as coded diagnostics.

warnings

Suspicious-but-recoverable findings.

provenance

The complete record of decisions taken on your data.

recovery

RecoveryReport when salvage engaged, else None.

chiptime.result.RecoveryReport dataclass

What salvage did, when it had to. Present on the result only if truncation recovery or resynchronization engaged — its absence means the file needed none.

Attributes:

Name Type Description
recovered_records int

Data messages decoded despite the damage.

estimated_total_records int | None

Best estimate of what a healthy file held.

bytes_read int

Bytes successfully consumed.

bytes_skipped int

Bytes stepped over as unreadable.

resync_count int

Times the reader re-anchored past corruption.

chiptime.result.SourceInfo dataclass

Identity of the parsed input. The local path is kept for humans but never serialized (privacy + determinism, ADR-0002); sha256 is the stable identity — cache and dedupe on it.

Attributes:

Name Type Description
path str | None

Where the bytes came from locally, or None for in-memory input.

size_bytes int

Input size after any unwrapping.

sha256 str

Hash of the parsed bytes.

unwrapped tuple[str, ...]

Containers removed on the way in (("gzip",), ...).

The workout model

Activity is the semantic view of one activity part; Session is the unit almost everything operates on — analytics functions take a Session, splits and intervals are computed per session, and multisport files simply have several.

chiptime.model.Activity dataclass

The whole workout: every session plus file-level context.

Attributes:

Name Type Description
sessions list[Session]

One per sport bout, in order (multisport gives several).

events list[Event]

Timer and device events (start/stop/battery/...).

gaps list[Gap]

Recording holes across the timeline, each classified.

device DeviceInfo | None

Recording device identity, when the file says.

athlete AthleteProfile | None

Athlete profile fields, when present.

local_timestamp str | None

Raw local-time string from the activity message.

utc_offset_s int | None

Validated local-UTC offset (ADR-0005), or None.

hrv_intervals_s list[float]

Beat-to-beat RR intervals when the file logged HRV.

chiptime.model.Session dataclass

One continuous bout of one sport — the center of the model.

A workout has one session per sport segment (a triathlon has five: swim, transition, bike, transition, run). Everything hangs off it: per-second data (records), declared structure (laps, lengths), and the declared-vs-derived totals pair.

Attributes:

Name Type Description
sport str

FIT sport name ("running", "cycling", ...).

sub_sport str | None

Refinement ("lap_swimming", "open_water", ...).

start_time datetime | None

Session start.

end_time datetime | None

Start + declared elapsed when known.

laps list[Lap]

Declared laps in order.

lengths list[Length]

Pool lengths (swims only).

records Records

The per-second timeline.

declared Totals | None

The device's totals, if its session message survived.

derived Totals

Totals recomputed from the records — always present.

discrepancies list[Discrepancy]

Where declared and derived disagree materially.

rebuilt bool

True when no session message survived and this one was synthesized from the records (recorded in provenance).

chiptime.model.Totals dataclass

One set of summary numbers for a session or lap.

Appears twice on a Sessiondeclared (the device's claim, absent if the message never arrived) and derived (recomputed from the records). Keeping both is the point: devices lie, and the disagreement is signal (see Discrepancy).

Attributes:

Name Type Description
elapsed_time_s float | None

Wall-clock span, pauses included.

timer_time_s float | None

Time with the timer running.

moving_time_s float | None

Time actually moving (derived only).

distance_m float | None

Distance in meters.

ascent_m float | None

Total climb in meters.

descent_m float | None

Total descent in meters.

calories_kcal float | None

Energy as reported.

avg dict[str, float]

Mean per stream name ({"power": 187.0, ...}).

max dict[str, float]

Maximum per stream name.

chiptime.model.Records dataclass

The per-second timeline, stored as columns rather than rows.

One shared time axis plus one Stream per field that ever appeared in a record — lossless (unknown fields become streams too) and analytics-friendly. Row-oriented access is a view (rows), not the storage.

Attributes:

Name Type Description
time list[datetime | None]

Record timestamps (None where a record carried no time).

streams dict[str, Stream]

Stream name → Stream, index-aligned with time.

to_pandas

to_pandas() -> Any

DataFrame view (requires the chiptime[pandas] extra). None stays NaN/NA — never silently zero (taxonomy #64).

chiptime.model.Stream dataclass

One column of record data — every FIT record field becomes a stream.

The honesty rule lives here: in values, None means the sensor said nothing (dropout, sentinel on the wire) and 0 means it said zero (coasting). They are never conflated, so a wire sentinel can never leak into an average.

Attributes:

Name Type Description
name str

Stream name — the FIT field name, or a promoted developer-field name like stryd_power.

units str | None

Unit string from the profile ("bpm", "m/s"), if known.

values list[Any]

One entry per record, index-aligned with Records.time.

source str

"native" for profile fields, "developer:<vendor>" or "developer" for developer fields.

chiptime.model.Lap dataclass

One declared lap. end_time is always start + elapsed — never the message's write timestamp, which devices emit late (taxonomy #50).

Analytics note: whether a lap was a button press or an auto-lap lives in the raw lap message (lap_trigger), read by chiptime.metrics.intervals.detect_structure — pass result.messages.

chiptime.model.Length dataclass

One pool length — the atom of swim structure. length_type is "active" for swum lengths and "idle" for wall rest; zero-length wall artifacts are flagged during reconciliation, not silently dropped.

chiptime.model.Gap dataclass

A hole in the recording, classified with evidence — an auto-pause is not corruption, and the kind says which is which.

Attributes:

Name Type Description
start datetime

Last good timestamp before the hole.

end datetime

First timestamp after it.

duration_s float

Length of the hole in seconds.

kind str

smart_recording | auto_pause | manual_stop | post_timer | corruption | unknown.

evidence str

Human-readable reason this classification was chosen.

chiptime.model.Discrepancy dataclass

A disagreement between what the device declared and what the records prove — surfaced, never silently reconciled.

Attributes:

Name Type Description
field str

Totals field name ("distance_m", ...).

declared float

The device's number.

derived float

The recomputed number.

delta float

derived - declared.

Messages — the lossless middle layer

Below the workout model sits the decoded message list: every message in file order, unknown-tolerant, with both decoded values and raw wire values. The semantic model is derived from these; nothing is lost in between. Analytics functions accept result.messages to read fields the semantic model doesn't surface (lap triggers, workout steps, pool length).

chiptime.message.Message dataclass

A decoded FIT data message, unknown-tolerant (contract #6).

chiptime.message.FieldValue dataclass

One decoded field. value is scaled/normalized with sentinels → None; raw is the wire value (kept for round-trips and include_raw output).

Repair and validation results

chiptime.repair.RepairResult dataclass

A repaired file plus the proof of what repair did.

Attributes:

Name Type Description
data bytes

The complete, valid .fit bytes — write them to disk as-is.

provenance list[ProvenanceEntry]

Every salvaged, synthesized, and dropped element.

output_strict_ok bool

Self-check — the output re-parsed in strict mode.

parse_result ParseResult | None

The salvage parse of the input, for inspection.

chiptime.validate.Finding dataclass

One platform-acceptance issue: a severity level, a stable code, and the human reason — encoding the checks that actually make uploads fail.