study_import
Bulk study metadata & lights: the import contract.
The browser (or any client) parses CSV/XLSX itself and maps columns to this vocabulary; the SERVER decides — these JSON batch requests validate, resolve study names, coerce times and apply per item with the platform's per-item error convention. There is no file-shaped write path.
Two rules carry the sharp edges:
- Field presence is the PATCH semantics: an absent field leaves the
study unchanged, an explicit
nullclears it. Clients must serialize withexclude_unset— a partial re-upload can never silently wipe data. - Clock time is the primary lights spelling (
"23:15"), resolved per study against its recording's own wall-clock frame with overnight wrap — accepted only while the study's recording span is ≤ 24 hours (recordings may run for days, making a bare clock time ambiguous; such rows error per item and must use the datetime or elapsed-ms spelling). Exactly one spelling per marker.
STUDY_IMPORT_COLUMNS = [StudyImportColumn(key='study', kind='study_key', aliases=['study', 'study_name', 'name', 'study_id', 'id'], description='The study each row addresses, by exact name or id. Required.'), StudyImportColumn(key='subject_key', kind='text', aliases=['subject_key', 'subject', 'subject_id', 'subject_code', 'participant', 'participant_id'], description="The person the study belongs to. Rows sharing a key within the project form one subject's series. Compared case- and whitespace-insensitively; blank leaves the study standing alone."), StudyImportColumn(key='session_index', kind='integer', aliases=['session_index', 'session', 'night', 'night_index', 'visit', 'visit_index'], description="Declared position of the study in its subject's series, 1 = first. Blank orders by recording start instead. A subject whose studies are only partly numbered is left out of session-indexed figures and named in a warning."), StudyImportColumn(key='age', kind='integer', aliases=['age', 'age_years', 'subject_age'], unit='years', description='Subject age in years (0–125). NULL clears.'), StudyImportColumn(key='sex', kind='sex', aliases=['sex', 'gender'], description="Subject sex (the platform's Sex vocabulary). NULL clears."), StudyImportColumn(key='bmi', kind='decimal', aliases=['bmi', 'body_mass_index'], unit='kg/m²', description='Body mass index (5–200). NULL clears.'), StudyImportColumn(key='lights_off_clock', kind='clock', aliases=['lights_off', 'lights_off_clock', 'lights off', 'loff'], description="Lights-off as wall clock HH:MM[:SS] in the recording's own frame, resolved with overnight wrap. Only valid while the study's recording span is ≤ 24 h — longer spans must use the datetime or ms spelling."), StudyImportColumn(key='lights_on_clock', kind='clock', aliases=['lights_on', 'lights_on_clock', 'lights on', 'lon'], description='Lights-on as wall clock HH:MM[:SS], resolved with overnight wrap after lights-off.'), StudyImportColumn(key='lights_off_datetime', kind='datetime', aliases=['lights_off_datetime', 'lights_off_at'], description="Lights-off as an absolute datetime in the recording's wall-clock frame (alternate spelling)."), StudyImportColumn(key='lights_on_datetime', kind='datetime', aliases=['lights_on_datetime', 'lights_on_at'], description='Lights-on as an absolute datetime (alternate spelling).'), StudyImportColumn(key='lights_off_ms', kind='elapsed_ms', aliases=['lights_off_ms', 'lights_off_elapsed_ms'], unit='ms', description='Lights-off as milliseconds from recording start (alternate spelling).'), StudyImportColumn(key='lights_on_ms', kind='elapsed_ms', aliases=['lights_on_ms', 'lights_on_elapsed_ms'], unit='ms', description='Lights-on as milliseconds from recording start (alternate spelling).')]
module-attribute
¶
The one column table: frontend auto-mapping, SDK docs, and the template download all derive from this — never duplicate it.
BatchPatchStudiesMetadataRequest
pydantic-model
¶
Bases: BaseModel
Apply metadata patches across many studies (207 per-item results).
Fields:
-
items(list[StudyMetadataPatch])
BatchPatchStudiesMetadataResponse
pydantic-model
¶
Bases: BaseModel
Per-item results plus the usual batch summary.
Fields:
-
results(list[StudyMetadataResult]) -
summary(BatchStudyMetadataSummary)
BatchSetStudyLightsRequest
pydantic-model
¶
Bases: BaseModel
Assign lights markers across many studies (207 per-item results).
Fields:
-
items(list[StudyLightsAssignment]) -
replace_existing(bool)
replace_existing = False
pydantic-field
¶
False: rows whose targeted marker already exists error per-item (manual edits win by default). True: overwrite.
BatchSetStudyLightsResponse
pydantic-model
¶
Bases: BaseModel
Per-item results plus the usual batch summary.
Fields:
-
results(list[StudyLightsResult]) -
summary(BatchStudyMetadataSummary)
BatchStudyMetadataSummary
pydantic-model
¶
Bases: BatchSummary
Summary for a metadata/lights batch.
Fields:
-
total(int) -
updated(int) -
errors(int)
LightsInstant
pydantic-model
¶
Bases: BaseModel
One lights marker in exactly one spelling.
clock is primary: wall clock in the recording's own frame, resolved
per study with overnight wrap (a 23:30 recording start with
lights_off 00:15 lands the next calendar day). datetime is an
absolute wall-clock datetime in the same frame; elapsed_ms counts
from recording start.
Fields:
-
clock(ClockTimeStr | None) -
datetime(str | None) -
elapsed_ms(NonNegativeInt | None)
Validators:
-
_exactly_one_spelling
clock = None
pydantic-field
¶
Wall clock "HH:MM" or "HH:MM:SS". Only resolvable while the study's recording span is ≤ 24 h; longer spans error per item (use datetime or elapsed_ms).
datetime = None
pydantic-field
¶
ISO datetime in the recording's wall-clock frame (any timezone suffix is ignored; times are read in the recording's own wall-clock frame).
StudyImportColumn
pydantic-model
¶
Bases: BaseModel
One column the import understands — drives auto-mapping and docs.
Fields:
-
key(str) -
kind(ColumnKind) -
aliases(list[str]) -
unit(str | None) -
description(str)
aliases
pydantic-field
¶
Lower-cased header spellings that auto-map to this column.
description
pydantic-field
¶
What the column sets.
key
pydantic-field
¶
Canonical column key (the API field name).
kind
pydantic-field
¶
Value shape, for parsing/validation.
unit = None
pydantic-field
¶
Display unit, if any.
StudyLightsAssignment
pydantic-model
¶
Bases: BaseModel
One study's lights markers.
Presence semantics as everywhere in this module: an absent marker is left unchanged, an explicit null DELETES the marker. At least one of the two must be present.
Fields:
-
study(str) -
lights_off(LightsInstant | None) -
lights_on(LightsInstant | None)
Validators:
-
_something_to_do
StudyLightsResult
pydantic-model
¶
Bases: BaseModel
Outcome for one lights row.
Fields:
-
study(str) -
study_id(UUID | None) -
status(Literal['updated', 'error']) -
lights_off_ms(int | None) -
lights_on_ms(int | None) -
error(BatchItemError | None)
StudyMetadataPatch
pydantic-model
¶
Bases: BaseModel
One study's metadata changes.
Field PRESENCE is the semantics: absent = unchanged, explicit null =
clear. Serialize with exclude_unset. Bounds match the canonical StudyMetadata (the batch-creation item is
harmonised to the same).
Fields:
-
study(str) -
age(NonNegativeInt | None) -
sex(Sex | None) -
bmi(PositiveFloat | None) -
subject_key(SubjectKey | None) -
session_index(PositiveInt | None)
Validators:
-
_something_to_do
session_index = None
pydantic-field
¶
Declared position in the subject's series; null derives it.
study
pydantic-field
¶
Study name or id. Names resolve within the project: 0 matches → not_found, >1 → conflict (rename or use ids).
subject_key = None
pydantic-field
¶
The subject this study belongs to; null detaches it.
StudyMetadataResult
pydantic-model
¶
Bases: BaseModel
Outcome for one metadata patch row.
Fields:
-
study(str) -
study_id(UUID | None) -
status(Literal['updated', 'error']) -
error(BatchItemError | None)