Skip to content

DataFrame builders, conversions, and the cookbook recipe (issue #6, PR 2) - #45

Open
craigmcchesney wants to merge 2 commits into
feature/issue-6-datasets-annotationsfrom
feature/issue-6-data-frame
Open

DataFrame builders, conversions, and the cookbook recipe (issue #6, PR 2)#45
craigmcchesney wants to merge 2 commits into
feature/issue-6-datasets-annotationsfrom
feature/issue-6-data-frame

Conversation

@craigmcchesney

Copy link
Copy Markdown
Collaborator

Phases 2–4 of issue #6, completing the ticket. Plan: plan/tickets/6/plan.md.

Stacked on #44 — based on that branch, so review it first; this PR retargets to main once #44 merges.

Phase 2 — data_frame.py

Builders for common.DataFrame, the shared time-series payload. It is also ingestion's ingestionDataFrame, so #17 extends this module rather than forking it.

  • sampling_clock() / timestamp_list() / timestamp_count() relocated here from sample_status_client now that calculations frames are a second caller, and re-exported from it so existing imports keep working.
  • Typed scalar column builders; data_column(), the escape hatch whose None entries become unset oneofs — the only way to express a gap on a shared axis; and the provenance helpers.
  • data_frame() routes columns by type and enforces the server's shape rules client-side (non-blank names, non-empty values, count match, name uniqueness across all types) so an error names the offending column. Size caps stay server-side: they are deployment policy, and duplicating numbers that can change is how clients drift.
  • Array/image/struct/serialized builders are deliberately Add ingestion API client (full surface: ingestData + streaming) #17's; hand-built columns of those kinds pass through today.

Phase 3 — data_frame_conversions.py

Pure Python (no extras): data_frame_timestamps(), column_values() — written standalone so the bucket query (#16) can reuse it — data_frame_columns(), column_metadata_dict().

Behind [analysis]: data_frame_to_pandas(), data_frame_from_pandas(), and the calculations_* bridges.

Two decisions worth a look:

  • The pandas direction always emits a TimestampList, never an inferred SamplingClock. A clock is only correct if the spacing is exactly uniform at nanosecond precision, and inferring that from an index that merely looks regular would quietly move timestamps.
  • NaN is fail-loud, with a message pointing at the two ways to express a gap. A dense typed column cannot represent one, so the alternatives are inventing a value or dropping a row.

Phase 4 — cookbook and docs

doc/cookbook/datasets-and-annotations.md continues the shared worked example (a dataset over the CXI_3443 shift's first hour, an orbit-drift annotation, a 1 Hz RMS calculation with provenance, the export). Plus the cookbook README entry, the README move of the three Annotation Service bullets from TODO to Current state, and the CLAUDE.md usage section promised in PR 1.

Two things found while building this

  • A naming collision I introduced. Exporting the data_frame() function from dp_python_lib.client shadowed the data_frame module, so both import forms returned the function — breaking the from dp_python_lib.client import data_frame as dfb form the plan's own reference snippet uses. The function is no longer re-exported at package level; reach it as dfb.data_frame(...).
  • An array column with no dims was treated as an empty product of 1, silently making every element its own sample. Caught by a test expecting a rejection; fixed in both the write and read paths.

Verification

  • 699 tests with the [analysis] extra; 601 pass and 56 skip cleanly without it, verified in a real pandas-free venv rather than assumed.
  • 25 integration tests + 12 subtests against dp-service fddf692, now including builder-made calculations read back on a sub-second axis — so the round trip exercises nanosecond arithmetic rather than whole seconds a float could also represent — provenance survival, and four export cases.
  • All 101 cookbook snippets type-check. The CLAUDE.md snippet was extracted and run against the live server, not just type-checked: save, query, batch fetch, calculations read-back in both plain Python and pandas, export, and teardown all succeed end to end.
  • ruff lint and format clean.

Not in this PR

Array/image/struct column builders (#17), bucket-query conversions (#16), and the attributes() / optional-criteria back-port to the older helpers (#40 / #41) — all out of scope per the plan.

🤖 Generated with Claude Code

https://claude.ai/code/session_019he3UCsAnqTDE2VQ73Djwn

craigmcchesney and others added 2 commits September 9, 2026 14:13
Adds the calculations construction and read-back layer the Phase 1 clients
accept, per plan D6/D7.

data_frame.py (no optional dependencies):
- sampling_clock() / timestamp_list() / timestamp_count() relocated here from
  sample_status_client now that calculations frames are a second caller, and
  re-exported from it so existing imports keep working.
- Typed scalar column builders, the legacy data_column() escape hatch (a None
  entry becomes an unset oneof -- the only way to express a gap on a shared
  axis), and the provenance helpers.
- data_frame() routes columns by type and enforces the server's SHAPE rules
  client-side, so an error names the offending column instead of bouncing the
  whole save.  Size caps stay server-side: they are deployment policy.
- Array/image/struct/serialized builders are deliberately #17's; hand-built
  columns of those kinds pass through data_frame() today.

data_frame_conversions.py:
- Pure Python: data_frame_timestamps() (integer nanoseconds throughout),
  column_values() (standalone, so the bucket query #16 can reuse it; array
  columns reshape to one list per sample), data_frame_columns(),
  column_metadata_dict().
- Behind [analysis]: data_frame_to_pandas() (UTC index from int64 nanos),
  data_frame_from_pandas() (dtype -> typed column, NaN fail-loud since a dense
  typed column cannot express a gap), and the calculations bridges.  The
  pandas direction always emits a TimestampList rather than inferring a
  SamplingClock: a clock is only correct if the spacing is exactly uniform at
  nanosecond precision, and guessing would move timestamps.

Two things surfaced while building this:
- The data_frame() *function* is deliberately NOT re-exported from
  dp_python_lib.client.  Binding that name shadows the data_frame *module*, so
  both import forms hand back the function -- breaking the documented
  `from dp_python_lib.client import data_frame as dfb` usage.
- An array column with no dims must be treated as underivable, not as an empty
  product of 1, which would silently make every element its own sample.  Fixed
  in both the write and read paths.

87 new unit tests: 657 total with the [analysis] extra, and 601 passing with
56 skipping cleanly without it (verified in a venv that has no pandas).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019he3UCsAnqTDE2VQ73Djwn
…ase 4)

Completes issue #6 with the documentation and the remaining integration
coverage.

doc/cookbook/datasets-and-annotations.md continues the cookbook's shared
worked example -- a dataset over the first hour of the CXI_3443 shift, an
orbit-drift annotation, a 1 Hz RMS calculation carrying provenance back to the
source PV, and the export -- and covers the things most likely to bite:

- The archive-existence rule, including why its error text ("no PV metadata
  found for names") misdescribes the check it failed.
- Calculations belonging to their annotation, so a full-replace save without
  them deletes them.
- Gaps: the typed columns are dense, so data_column() or a separate frame.
- Why the pandas direction never infers a SamplingClock, and why NaN is
  fail-loud.
- That there is no download: file_path is a server-side path.

Integration test grew the Phase 4 legs: builder-made calculations read back
through data_frame_conversions on a sub-second axis (so the round trip
exercises nanosecond arithmetic rather than whole seconds a float could also
represent), provenance survival, and four export cases. 25 tests, 12 subtests,
all passing against dp-service fddf692.

Also: README moves the three Annotation Service bullets from TODO to Current
state; CLAUDE.md gains the usage section promised in PR 1; the cookbook README
lists the recipe and extends the worked-example description; and the snippet
checker's preamble gains the new names plus the recipe's shared handles.

The CLAUDE.md snippet was extracted and RUN against the live server, not just
type-checked -- save, query, batch fetch, calculations read-back in both plain
Python and pandas, export, and teardown all succeed end to end.

699 tests pass with the [analysis] extra; 601 pass and 56 skip cleanly without
it. ruff, format, and all 101 cookbook snippets clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019he3UCsAnqTDE2VQ73Djwn
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant