Skip to content

Apply trace_options in every datasource section (issue #80) - #86

Merged
AlexisJanin merged 1 commit into
mainfrom
issue-80-trace-options-everywhere
Aug 25, 2026
Merged

Apply trace_options in every datasource section (issue #80)#86
AlexisJanin merged 1 commit into
mainfrom
issue-80-trace-options-everywhere

Conversation

@AlexisJanin

Copy link
Copy Markdown
Collaborator

Resolves the silent-ignore described in #80, taking the "make it work everywhere" branch of the decision that issue asked for.

Why that option

The three subsystems already disagreed, and two of them implemented "everywhere":

  • database_options_parser.py validates trace_options in every datasource section.
  • database_options_xlsx.py writes it from the sentinel row for any datasource, with tests pinning that.
  • The user guide's Excel column table already documented trace_mode as "for every trace in this datasource" — the published docs promised behaviour that did not exist.

Restricting to other:: would have meant removing working behaviour from two places and adding a rejection path to each. Making it work everywhere is a 4-line merge that renders a 14-line bridge redundant.

The gap

Two constants share the string "trace_options", which is what hid this:

Constant Written by Read by
DatabaseOptions.TRACE_OPTIONS the user, in a database_options section nobody, except one bridge in other/
SourceOptions.TRACE_OPTIONS a datasource module, in its options.py Signal._build_trace_options

_build_trace_options already received both dicts and read only the second, so a user block never reached the renderer. other worked because _source_options_for_file pre-merged the user's keys into source_options before the call.

Changes

  • signal_container.py — merge the two, user block over module defaults, key by key. A non-dict block is tolerated, since validation reports it as an error without aborting the run.
  • other/find_load_format.py — drop _source_options_for_file (−20 lines). Because other passes the per-file block as database_options_specific, the merge above is the same operation on the same two dicts in the same order.
  • constants.py — both TRACE_OPTIONS comments rewritten; the old one asserted "Only 'other' reads it", and SourceOptions had no docstring at all.

Behaviour change

A config that already sets trace_options (or the Excel trace_mode / line_width / opacity / marker_symbol columns) on a device datasource starts taking effect, where it was previously ignored. Where that datasource ships its own trace style — fluxmed_parameters, mindray_respi_numerics, mindray_respi_waves, other — the user's block now wins per key; keys left unset keep the shipped value.

That direction matches the existing tier ordering (explicit config beats shipped default) and is how other already behaved. Nothing changes for a config that only styled other::<stem> files. Recorded in CHANGELOG.md under Unreleased.

Tests

  • tests/unit/test_signal_container.py::TestTraceOptionsPrecedence — six cases: precedence in both directions, the two malformed inputs validation lets through (misspelled key, non-dict block), and the invariant that per-signal line_dash still wins.
  • tests/datasource/test_mindray_respi_waves.py::TestTraceOptions — the acceptance criterion's non-other datasource. Chosen because it ships its own trace_options, so it proves both directions at once.
  • tests/datasource/test_other.py::TestPerFileTraceOptions is the regression guard on the deletion and is unchanged.

ruff check + ruff format --check clean; 878 passed. No snapshot regeneration needed — trace styling is well downstream of the parquet goldens.

Docs and demo

  • The user guide had no trace_options row in the Per-Source Fields Reference at all; the key existed only inside the "Generic Other" narrative. Added the row, the per-source JSON example, and a ### trace_options Block section modelled on the numerics one. The other:: passage now cross-links instead of duplicating the key list.
  • Demo database: eit now sets opacity: 0.7, so the shipped example exercises the block outside other::. Its nine overlaid impedance curves (Global + Local 1–4 + %Local 1–4) stay legible where they cross. Verified end-to-end — opacity=[1.0] without the block, opacity=[0.7] with it. The .json is regenerated from the .xlsx with the one-liner in test_example_assets.py.
  • CONTEXT.md gains a Trace entry. The glossary previously listed "trace" only as a word to avoid (as a synonym for Signal), which left trace_options looking like a violation of the project's own vocabulary. A Trace is the drawn form of a Signal — which is exactly why the styling key is not signal_options. The two-tier name collision is flagged alongside the existing numerics and process entries.

Acceptance criteria

  • trace_options in a non-other section either takes effect or produces a warning — it now takes effect.
  • A test covers the chosen behaviour for at least one non-other datasource.
  • Tutorial updated to state which scopes accept it.

🤖 Generated with Claude Code

`trace_options` was validated in any datasource section and written by the
XLSX sentinel row for any datasource, but read at exactly one site — inside
`other::` handling. Put a block on `servo_u` or `eit` and it validated
cleanly, warned about nothing, and did nothing.

Two constants hid the gap by sharing a string: `DatabaseOptions.TRACE_OPTIONS`
is what a config file writes, `SourceOptions.TRACE_OPTIONS` what a module's
options.py ships, and only the latter was ever read.

- Merge the two in `Signal._build_trace_options`, user block over module
  defaults, key by key. Per-signal `color` / `line_dash` / `visible` in the
  `signals` block stay the last word.
- Drop `_source_options_for_file` from the `other` source: it pre-merged the
  per-file block into `source_options` before the call, which the merge above
  now covers for every scope.
- Cover the merge in `tests/unit/test_signal_container.py` (precedence in both
  directions, plus the two malformed inputs validation lets through) and on a
  module datasource in `tests/datasource/test_mindray_respi_waves.py`, which
  ships its own trace style to override.

Behaviour change: a config that already sets `trace_options` on a device
datasource starts taking effect, and where that datasource ships its own trace
style the config now wins per key. Recorded in the changelog.

- Document the block in the user guide, which had no `trace_options` row in
  the per-source reference at all — it existed only in the "Generic Other"
  narrative, while the Excel column table already promised it worked
  datasource-wide.
- Set `opacity` on `eit` in the demo database, so the shipped example
  exercises the block outside `other::`: the nine overlaid impedance curves
  are legible where they cross. The `.json` is regenerated from the `.xlsx`.
- Add a **Trace** entry to CONTEXT.md. The glossary listed "trace" only as a
  word to avoid, as a synonym for Signal, leaving `trace_options` looking like
  a violation of it; a Trace is the drawn form of a Signal, which is why the
  key is not `signal_options`. Flag the two-tier name collision alongside the
  existing `numerics` and `process` entries.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@AlexisJanin AlexisJanin self-assigned this Aug 25, 2026
@AlexisJanin AlexisJanin added the Code quality Improve overall code quality (maintainability, robustness, readability) label Aug 25, 2026
@AlexisJanin AlexisJanin linked an issue Aug 25, 2026 that may be closed by this pull request
3 tasks
@AlexisJanin
AlexisJanin merged commit 4313a88 into main Aug 25, 2026
3 checks passed
@AlexisJanin
AlexisJanin deleted the issue-80-trace-options-everywhere branch August 25, 2026 09:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Code quality Improve overall code quality (maintainability, robustness, readability)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

trace_options is accepted in ten config scopes but read in only one

2 participants