Skip to content

V_epsilon: implement Brainstorm E observation tier + shared shape library#62

Merged
stevevanhooser merged 16 commits into
mainfrom
claude/optimistic-ritchie-f06jvs
Jul 4, 2026
Merged

V_epsilon: implement Brainstorm E observation tier + shared shape library#62
stevevanhooser merged 16 commits into
mainfrom
claude/optimistic-ritchie-f06jvs

Conversation

@stevevanhooser

Copy link
Copy Markdown
Contributor

Implements Brainstorm E (identity-named tiers + a shared data-shape library) in V_epsilon, replacing the Option-A observation tier. A is removed outright (not deprecated) — no one is using A in early development.

See the developed catalog this implements: ndi-next-stepsSummer 2026/1_Ingestion/20260615/Brainstorm_E_Class_Catalog.md.

What changed

Shape library (new — abstract mixin classes, ← base)

The typed value-composites are promoted to named classes that own the value field and are mixed into the identity classes as a second superclass (resolves the E parent's open issue #7):
scalar_mass, scalar_length, scalar_duration, scalar_volume, scalar_temperature, scalar_pressure, scalar_frequency, scalar_voltage, scalar_current, scalar_concentration, scalar_count, scalar_score, generic_scalar, categorical_concept.

The same shape class is inherited by the observation that reads it and the manipulation that imposes it — e.g. scalar_temperature ← both core_temperature_observation and temperature_manipulation — so value is defined once and isa scalar_temperature sweeps both tiers. Uses the existing mixin pattern (cf. epochid in epoch_relative_reference); no meta-schema change.

Observation tier — named by property (E)

Scalar dimension classes replaced by property classes (class isa scalar_observation, scalar_<unit>):
body_weight_observation, organ_volume_observation, body_length_observation, age_observation, core_temperature_observation, heart_rate_observation, respiration_rate_observation, blood_pressure_observation, litter_size_observation, cell_count_observation, body_condition_observation, behavioral_score_observation, concentration_observation, membrane_potential_observation, generic_scalar_observation.

categorical_observation becomes an abstract genus; concrete property classes inherit categorical_concept: developmental_stage_observation (pinned root UBERON:0000105), health_status_observation, behavioral_phenotype_observation, pigmentation_observation, estrous_stage_observation, generic_categorical_observation.

Removed A classes: mass_/length_/duration_/temperature_/pressure_/count_/score_/frequency_/volume_observation.

Series/expression families unchanged (already E-compatible, file-backed); no opaque_/standalone_/daq_ leaves, per Standalone_Removal_Decision.

Plumbing

index.json registrations, topics.json (new shape_library group + refreshed observations group), and the V_epsilon_SPEC.md observation section.

Validation

All 96 schema tests pass locally. Field flattening verified: e.g. body_weight_observation resolves value (← scalar_mass) + measured_property/target_structure (← observation) + subject_id/time_reference (← subject_interaction).

Deferred (not in this PR)

  • Series-as-cardinality (value as array + parallel time_reference) — needs a meta-schema array change.
  • The advisory binding registry as standalone files — categorical classes use the existing inline binding constraint for now.
  • Manipulation tier left as-is (already action-named).

🤖 Generated with Claude Code


Generated by Claude Code

claude added 5 commits June 17, 2026 21:15
Replaces the Option-A dimension-named scalar observation classes with
Brainstorm E identity-named (property-named) classes; the typed composites
become the shared shape library each property class pins as its value type.
A is removed outright (not deprecated) per "no one is using A" in early dev.

Scalar observations (isa scalar_observation; value pins a typed composite):
  body_weight_observation (mass), organ_volume_observation (volume),
  body_length_observation (length), age_observation (duration),
  core_temperature_observation (temperature),
  heart_rate_observation / respiration_rate_observation (frequency),
  blood_pressure_observation (pressure),
  litter_size_observation / cell_count_observation (count),
  body_condition_observation / behavioral_score_observation (score),
  concentration_observation (concentration),
  membrane_potential_observation (voltage),
  generic_scalar_observation (escape hatch, kept).
Removed: mass_/length_/duration_/temperature_/pressure_/count_/score_/
  frequency_/volume_observation.

Categorical observations: categorical_observation is now an abstract genus;
concrete classes are named by property (full E):
  developmental_stage_observation (pinned root UBERON:0000105),
  health_status_observation, behavioral_phenotype_observation,
  pigmentation_observation, estrous_stage_observation,
  generic_categorical_observation (escape hatch, free ontology_term).

Series/expression families unchanged (already E-compatible, file-backed);
no opaque_/standalone_/daq_ leaves, per Standalone_Removal_Decision.

Updates index.json registrations, topics.json observation group, and the
V_epsilon_SPEC.md observation section. All 96 schema tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ABEeDJ83r9Hq9PaiSije8o
Promotes the typed value-composites to named abstract shape classes that own
the `value` field, and rewires both tiers to inherit them as a second
superclass (resolving E parent open issue #7):

  scalar_mass, scalar_length, scalar_duration, scalar_volume,
  scalar_temperature, scalar_pressure, scalar_frequency, scalar_voltage,
  scalar_current, scalar_concentration, scalar_count, scalar_score,
  generic_scalar, categorical_concept.

Each scalar observation/manipulation now reads e.g.
  body_weight_observation  isa scalar_observation, scalar_mass
  temperature_manipulation isa scalar_manipulation, scalar_temperature
so the same shape class is shared by the read (observation) and write
(manipulation) of that shape, `value` is defined once, and isa-by-shape
sweeps both tiers. Categorical property classes inherit categorical_concept
(developmental_stage and generic override the value binding).

Uses the existing mixin pattern (cf. epochid in epoch_relative_reference);
no meta-schema change. Updates index.json, topics.json (new shape_library
group), and V_epsilon_SPEC.md. All 96 schema tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ABEeDJ83r9Hq9PaiSije8o
…ation

Brainstorm E Part 2: a series is a higher cardinality of the same observation,
not a new class. Prototype (contained to one class):
- core_temperature_observation overrides `value` to an ARRAY of the temperature
  composite (mustBeScalar: false); a single reading is the length-1 case.
- adds `sample_time`, an array of duration offsets from the anchoring
  time_reference, element-aligned with value (value[i] at time_reference +
  sample_time[i]). This keeps the depends_on time_reference meaning "one moment,
  many frames" and puts the per-sample axis in embedded data.
- worked example: examples/core_temperature_observation_series.json (a 3-point
  cooling curve).
- tests/test_series_cardinality.py: asserts the array-of-structures shape and
  ships the len(value)==len(sample_time) check (D3) that JSON Schema cannot
  express on a single field.

The array-of-composite shape (not a bare numeric matrix) preserves the query
model's existential [*] search; same-sample value/time correlation is the
documented limit. No meta-schema change (mustBeScalar is already an independent
boolean). Full design + rollout: Series_As_Cardinality_Proposal.md
(ndi-next-steps). All 103 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ABEeDJ83r9Hq9PaiSije8o
ruff F401 — the new test module uses plain assertions, no pytest API.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ABEeDJ83r9Hq9PaiSije8o
…table_row→obs)

#1 of the migration work — textual conversion specifications for the E split:

- treatment.md: rewritten from the conservative class-preserving conversion to
  the E SPLIT — treatment dispatches by ontology branch to injection / bath /
  procedural_manipulation / temperature_manipulation (and other scalar_manip) /
  environmental_manipulation, with focal-vs-ambient and structure as data
  (target_structure), numeric_value → typed value or companion observation, the
  Dab string_value→target_structure edge case, and out-of-tier routing for
  non-manipulation rows. Mostly 1→1 with branch dispatch.

- ontology_table_row.md (new): the 1→N observation-tier split — each row → a
  scalar/categorical observation property class (or generic escape hatch), with
  subject-intrinsic and relational rows routed out of tier. Flags the required
  1→N migrator engine capability.

- _index.md: registers both split specs.

These are the spec the migration code (#2, DID-matlab) implements; they are
version-/engine-agnostic. Schema tests unaffected (103 pass).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ABEeDJ83r9Hq9PaiSije8o
claude added 11 commits June 19, 2026 23:54
…ative_reference

Two independent additions:

1. subject_statement / subject_assertion (Subject_Statement_Decision):
   - subject_statement (abstract, ← base): owns the subject_id anchor;
     timeless (no time_reference).
   - subject_assertion (concrete): timeless asserted facts —
     asserted_property + value + source (species/strain/sex/genotype).
   - subject_interaction re-parented under subject_statement; it adds the
     required time_reference_# (subject_id now inherited). Subclasses tighten
     (timed) the timeless parent; leaves still resolve subject_id +
     time_reference (verified by inheritance flatten).

2. session_relative_reference (← time_reference): an ORDINAL anchor relative
   to the session — dep session_id → session, relation enum {before, after,
   at_start_of, at_end_of, concurrent_with, during}, no metric offset. For
   interactions with neither a device clock nor a wall-clock date (e.g. an
   awake behavioral test "at the end of the session"). This is the ordinal,
   session-anchored sibling of event_relative (which is metric and targets a
   subject_interaction, neither of which fits the no-clock/no-date case).

Updates index.json, topics.json (new subject_statement group; session_relative
added to time_reference group), and V_epsilon_SPEC.md. All 103 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ABEeDJ83r9Hq9PaiSije8o
…he split specs

treatment.md and ontology_table_row.md now describe the implemented anchor:
each migrated interaction emits/links a session_relative_reference
(relation=during, -> session via base.session_id) instead of a synthesized
empty time_reference. Notes the 1->2 (treatment) and 1->N+1 (table, shared
anchor) cardinality and that the 1->N engine capability has landed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ABEeDJ83r9Hq9PaiSije8o
…aq-less anchor)

session_extent (<- base): depends_on session_id -> session; start/end timestamps
(UTC, at least one present), grounding enum {declared, epoch_derived,
reference_derived, developmental}, is_approximate. Lets a curator say
'the session began/ended at UTC x' for daq-less sessions; the same record is
derived from epochs when they exist. session_relative_reference resolves
through it (at_start_of->start, at_end_of->end, during->[start,end]), grounding
ordinal claims onto UTC. All 103 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ABEeDJ83r9Hq9PaiSije8o
…allback

Discovery showed the dominant form is parallel char fields (names/
variable_names/ontology_nodes/data), not a rows array; migrator falls back to
migrating unchanged as ontology_table_row pending the per-row split.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ABEeDJ83r9Hq9PaiSije8o
…-column observations)

Update the spec note from 'follow-up' to 'implemented': the parallel char-field
layout (names/variable_names/ontology_nodes/data) splits one row into per-column
observations; unrecognised layouts still fall back to migrating unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ABEeDJ83r9Hq9PaiSije8o
The dominant V_epsilon corpus quarantine (~47K docs: 41,095 JH + 6,205
Dab) was "Required field categorical_concept.value is missing". Root
cause: categorical_concept (the shape mixin) declared a required `value`
in its own block, but the overrider classes (developmental_stage_
observation, generic_categorical_observation) re-declared their own
`value`, so a migrated doc filled the concrete block and left the
inherited categorical_concept.value empty.

Resolve by flattening: categorical_concept.value now declares
placement: concrete_class, so `value` lives in each categorical
observation's OWN block and categorical_concept contributes no block.
The two re-declared `value` fields are removed (one value per concrete
class, no same-block name collision). developmental_stage_observation's
UBERON:0000105 root is now a documented convention rather than a
schema-enforced binding (the trade-off of the flatten approach).

Docs updated to match: from_did_v1/ontology_table_row.md and
V_epsilon_SPEC.md. All 103 schema tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ABEeDJ83r9Hq9PaiSije8o
subject_group is deprecated in V_epsilon and folds into the subject tier
(subject with is_group: true; membership becomes group_assignment events
assembled in the NDI layer). Add the conversion doc and index row; the
migrator lands in DID-matlab. No schema JSON change (subject.is_group and
group_assignment already exist). 103 schema tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ABEeDJ83r9Hq9PaiSije8o
Step 2 of Series_As_Cardinality_Proposal.md (accepted). Every scalar
shape mixin's `value` is now an array of its composite (mustBeScalar:
false) -- a single reading is the length-1 case, a curve is length N of
the same class -- and the scalar_observation / scalar_manipulation genera
carry a parallel, element-aligned `sample_time` array (optional; absent =>
single reading at the anchor).

core_temperature_observation no longer overrides value/sample_time; it
inherits the cardinality form from scalar_temperature / scalar_observation
like every other scalar property class. This also clears the JH corpus
quarantine "core_temperature_observation.value missing" (691): value now
lives in the inherited scalar_temperature block that the migrator already
fills, and mustBeScalar:false is strictly more permissive, so a single
reading validates as the length-1 case (same scalar-struct-is-length-1
convention target_structure already uses -- no migrator change needed).

Updates: 13 scalar mixins, scalar_observation/scalar_manipulation,
core_temperature_observation (override removed), the worked example
(value -> scalar_temperature block, sample_time -> scalar_observation
block), test_series_cardinality.py (assert the mixin-level array shape),
and V_epsilon_SPEC.md. 104 schema tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ABEeDJ83r9Hq9PaiSije8o
…atment_transfer folds

Document the three remaining deprecated-class folds (the migrators land in
DID-matlab): treatment_drug -> injection(kind:drug), virus_injection ->
injection(kind:virus, dilution in the mixture concentration), and
treatment_transfer -> biological_transfer (recipient->subject, donor
carried). Target classes already exist in draft; no schema JSON change.
104 schema tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ABEeDJ83r9Hq9PaiSije8o
Investigated the did_v1 document set: the subject_group body is empty and
no v1 document depends on a subject_group, so group membership is not
recorded anywhere in v1. There is therefore no source to migrate into
group_assignment events -- the subject_group -> subject(is_group) fold is
the complete migration, and group_assignment is a forward-looking class
for newly authored data, not a migration target. Update the conversion
doc so this is a recorded decision rather than a dangling TODO.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ABEeDJ83r9Hq9PaiSije8o
Move the 69 family-A migration classes from draft/ to stable/ and set
maturity_level: "stable": the subject_interaction spine, the
time_reference family, the scalar/categorical observation tiers + shape
mixins, the manipulation tiers, and the annotation/event classes
(group_assignment, placement, derivation, session_extent,
interaction_purpose, stimulus_approach). These run clean against the
discovery corpora (0 quarantine), so they are frozen for cutover.

Left in draft/ (40 classes): the genomics/omics + file-backed dataseries
data-format families (expression_*, sequence_read_data_*,
reference_sequence_data_* / reference_annotation_data_*, dataseries_* /
timeseries_* / imageseries_*, instrument) -- a separate effort not
exercised by the physiology corpora.

index.json tier/maturity/path updated for all 69; test_series_cardinality
resolves classes across tiers; SPEC tier-status note added. The assembled
epsilon-schemas set (stable + draft) is unchanged, so corpus validation is
unaffected. 104 schema tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ABEeDJ83r9Hq9PaiSije8o
@stevevanhooser stevevanhooser merged commit d92a41c into main Jul 4, 2026
4 checks passed
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.

2 participants