99import enum
1010import uuid
1111from datetime import datetime
12- from typing import Any , Optional
12+ from typing import Any , ClassVar , Optional
1313
1414from sqlalchemy import Column , DateTime , Enum as SAEnum , Index , JSON , func , select , text
1515from sqlalchemy .orm import column_property
2222# in, what a task measures, and how a model was trained for it.
2323
2424
25+ class DescribedEnum (str , enum .Enum ):
26+ """A str enum whose members carry the help text shown beside a form control.
27+
28+ Members are written ``name = value, description``, and the description rides on the
29+ member itself rather than sitting in a lookup table beside it — one place to read, and
30+ a member can't be added without one being noticed as missing.
31+
32+ ``str.__new__`` with ``_value_`` set explicitly is what keeps the tuple from becoming
33+ the value: a member still compares equal to its own string, ``Modality("spikes")``
34+ still resolves, and the SQLAlchemy ``Enum`` column and pydantic both see the same
35+ values they always did.
36+ """
37+
38+ def __new__ (cls , value : str , description : str = "" ):
39+ member = str .__new__ (cls , value )
40+ member ._value_ = value
41+ member .description = description
42+
43+ return member
44+
45+
2546class TeamRole (str , enum .Enum ):
2647 owner = "owner"
2748 collaborator = "collaborator"
@@ -61,40 +82,113 @@ class TaskType(str, enum.Enum):
6182 brain_region = "brain_region"
6283
6384
64- class Modality (str , enum .Enum ):
65- anatomy = "anatomy"
66- spikes = "spikes"
67- behavior = "behavior"
68- lfp = "lfp"
69- waveforms = "waveforms"
85+ class Modality (DescribedEnum ):
86+ anatomy = (
87+ "anatomy" ,
88+ "Brain region labels, assigned per-unit by mapping recording location to a reference "
89+ "brain atlas." ,
90+ )
91+ spikes = (
92+ "spikes" ,
93+ "Ephys spike trains (following spike band filter, threshold crossing, spike sorting). "
94+ "Discrete per-neuron event times, optionally binned into firing-rate estimates." ,
95+ )
96+ behavior = (
97+ "behavior" ,
98+ "Any behavioral signal relevant to the decision-making task, including task events and "
99+ "continuous traces from video and pose tracking." ,
100+ )
101+ lfp = (
102+ "lfp" ,
103+ "Ephys local field potential (following LFP band filter, downsampling). Captures "
104+ "lower-frequency population-level signals distinct from spike-level activity." ,
105+ )
106+ waveforms = (
107+ "waveforms" ,
108+ "Ephys spike waveforms (extracellular action potential shape captured in a short window "
109+ "around each detected spike)." ,
110+ )
70111
71112
72- class TrainingParadigm (str , enum .Enum ):
73- TSS = "TSS" # Task-Specific Supervised
74- TSU = "TSU" # Task-Specific Unsupervised (pretrained backbone)
75- single_session = "single_session"
113+ class TrainingParadigm (DescribedEnum ):
114+ TSS = (
115+ "TSS" ,
116+ "Task-Suite-Supervised (pretraining objective matches the supervision target of this "
117+ "task)." ,
118+ )
119+ TSU = (
120+ "TSU" ,
121+ "Task-Suite-Unsupervised (pretraining objective was unrelated to the supervision target "
122+ "of this task)." ,
123+ )
124+ single_session = (
125+ "single_session" ,
126+ "Trained from scratch on each individual session, no pretraining." ,
127+ )
76128
77129
78- class SupervisionRegime (str , enum .Enum ):
79- zero_shot = "zero_shot"
80- few_shot = "few_shot"
81- full = "full"
82- other = "other"
130+ class SupervisionRegime (DescribedEnum ):
131+ zero_shot = (
132+ "zero_shot" ,
133+ "No supervision data from this task is used to adapt the pretrained model to the eval "
134+ "session." ,
135+ )
136+ few_shot = (
137+ "few_shot" ,
138+ "A subset of the available supervised data for this task is used to calibrate the "
139+ "pretrained model." ,
140+ )
141+ full = (
142+ "full" ,
143+ "All available supervised data for this task is used to calibrate the pretrained model." ,
144+ )
145+ other = (
146+ "other" ,
147+ "A regime not captured above; please describe it in the private narrative." ,
148+ )
83149
84150
85- class Calibration (str , enum .Enum ):
86- inductive = "inductive" # gradient-free at eval time
87- transductive = "transductive" # requires gradients on eval set
151+ class Calibration (DescribedEnum ):
152+ inductive = (
153+ "inductive" ,
154+ "Model evaluated on this task with no parameter updates needed." ,
155+ )
156+ transductive = (
157+ "transductive" ,
158+ "Gradients used to update the model on this task, whether supervised directly on this "
159+ "task or calibrated via another objective." ,
160+ )
88161
89162
90- class FinetuningStrategy (str , enum .Enum ):
91- linear_probe = "linear_probe"
92- mlp_probe = "mlp_probe"
93- gradual_unfreezing = "gradual_unfreezing"
94- full_finetuning = "full_finetuning"
95- single_unit = "single_unit"
96- multi_unit = "multi_unit"
97- other = "other"
163+ class FinetuningStrategy (DescribedEnum ):
164+ linear_probe = (
165+ "linear_probe" ,
166+ "Linear readout trained on frozen pretrained representations." ,
167+ )
168+ mlp_probe = (
169+ "mlp_probe" ,
170+ "Multi-layer perceptron readout trained on frozen pretrained representations." ,
171+ )
172+ gradual_unfreezing = (
173+ "gradual_unfreezing" ,
174+ "Layers progressively unfrozen and finetuned over the course of adaptation." ,
175+ )
176+ full_finetuning = (
177+ "full_finetuning" ,
178+ "All model parameters updated during adaptation to this task." ,
179+ )
180+ single_unit = (
181+ "single_unit" ,
182+ "TS3 probe fit and evaluated per individual unit." ,
183+ )
184+ multi_unit = (
185+ "multi_unit" ,
186+ "TS3 probe fit using a consensus across multiple nearby units." ,
187+ )
188+ other = (
189+ "other" ,
190+ "A strategy not captured above; please describe it in the private narrative." ,
191+ )
98192
99193
100194class Metric (str , enum .Enum ):
@@ -105,6 +199,17 @@ class Metric(str, enum.Enum):
105199 r2 = "r2"
106200
107201
202+ # What each task suite asks a model to predict. Domain fact rather than a column: it is
203+ # fixed by what the suites are, and both the submission forms (which modality can't be an
204+ # *extra input* when it is the target) and /api/meta read it. Here so there is one copy —
205+ # it previously lived only in the frontend's task schema.
206+ SUITE_OUTPUT_MODALITY : dict [TaskSuite , Modality ] = {
207+ TaskSuite .ts1 : Modality .behavior ,
208+ TaskSuite .ts2 : Modality .spikes ,
209+ TaskSuite .ts3 : Modality .anatomy ,
210+ }
211+
212+
108213# ── Helpers ────────────────────────────────────────────────────────────────────
109214
110215
@@ -222,6 +327,32 @@ class Model(SQLModel, table=True):
222327 team : Team | None = Relationship (back_populates = "models" )
223328 submissions : list ["Submission" ] = Relationship (back_populates = "model" )
224329
330+ # Help text for the create and edit forms, keyed by field name and served by
331+ # /api/meta. Here rather than on the response schemas so the wording sits with the
332+ # column it describes and can't drift from it; ``test_models`` asserts every key is a
333+ # real field.
334+ FIELD_DESCRIPTIONS : ClassVar [dict [str , str ]] = {
335+ "link_project" : "Link to project homepage." ,
336+ "link_weights" : "Link to model weights (e.g. Huggingface)." ,
337+ "link_code" : "Link to model code (e.g. GitHub)." ,
338+ "publication_doi" : "DOI of affiliated publication." ,
339+ "n_parameters" : "Total number of non-embedding model parameters." ,
340+ "temporal_context_s" : (
341+ "Duration (s) of context window used, including and preceding the target window. "
342+ "If context length varies across tasks for this model, report the primary/default "
343+ "value here and note task-specific deviations in the submission narrative."
344+ ),
345+ "is_pretrained" : (
346+ "Is this a pretrained foundation model, or trained from scratch on every session?"
347+ ),
348+ "pretrained_in_modalities" : "If pretrained, which modalities were accepted as input." ,
349+ "pretrained_out_modalities" : "If pretrained, which modalities were used for supervision." ,
350+ "pretraining_data" : (
351+ "Describe the corpus of pretraining data used (all sessions, a subset, external "
352+ "data)."
353+ ),
354+ }
355+
225356
226357class Submission (SQLModel , table = True ):
227358 """Uploaded prediction zip + scoring state."""
@@ -262,6 +393,22 @@ class Submission(SQLModel, table=True):
262393 sa_relationship_kwargs = {"cascade" : "all, delete-orphan" },
263394 )
264395
396+ # See Model.FIELD_DESCRIPTIONS.
397+ FIELD_DESCRIPTIONS : ClassVar [dict [str , str ]] = {
398+ "label" : (
399+ "Name of this submission, identifying the base model and what distinguishes this "
400+ "particular variant."
401+ ),
402+ "narrative_public" : (
403+ "A narrative describing this submission, which is made public on the leaderboard."
404+ ),
405+ "narrative_private" : (
406+ "This is space for writing comments that are kept private, including notes to the "
407+ "administrators."
408+ ),
409+ "is_public" : "Is this submission ready to be published on the leaderboard?" ,
410+ }
411+
265412
266413class SubmissionUser (SQLModel , table = True ):
267414 """M2M bridge — Submission ↔ User."""
@@ -329,6 +476,49 @@ class TaskSubmission(SQLModel, table=True):
329476 sa_relationship_kwargs = {"cascade" : "all, delete-orphan" , "uselist" : False },
330477 )
331478
479+ # See Model.FIELD_DESCRIPTIONS.
480+ FIELD_DESCRIPTIONS : ClassVar [dict [str , str ]] = {
481+ "extra_input_modality" : (
482+ "Does this model require any modalities other than spikes as input for this task? "
483+ "This necessarily excludes task-related supervision targets within the target window."
484+ ),
485+ "training_paradigm" : (
486+ "Which paradigm is used to train this model on this task? Single-session models are "
487+ "trained from scratch on each session, with no pretraining used. If adapting a "
488+ "pretrained foundation model, did the pretraining objective match the supervision "
489+ "target of this task (Task-Suite-Supervised, TSS) or was it unrelated "
490+ "(Task-Suite-Unsupervised, TSU)? Note: TSS implies the training objective itself was "
491+ "aligned with this task, not just the modalities used in input and output (e.g., "
492+ "forecasting, not just spikes-to-spikes). Refer to the BrainWideBench paper for more "
493+ "details and examples with existing baselines."
494+ ),
495+ "supervision_regime" : (
496+ "To what degree is this model supervised on this task? Zero-shot means no supervision "
497+ "is needed to adapt a pretrained model on the eval session. Few-shot means a subset "
498+ "of the available supervised data is used to calibrate a pretrained model. Full means "
499+ "all available supervised data is used to calibrate a pretrained model. Note that "
500+ "this specifically refers to data pertaining to this task (i.e. supervision data), "
501+ "not other available data in the dataset. Single-session models implicitly cannot be "
502+ "used in a zero-shot fashion, though can use few-shot or use full supervision. If "
503+ "there is another paradigm not listed, please specify in the private description."
504+ ),
505+ "calibration" : (
506+ "Are gradients required to update a pretrained model in order to adapt to this task, "
507+ "whether that adaptation is supervised on this task directly or calibrated via some "
508+ "other objective (both count as transductive)? Or can it be evaluated on this task "
509+ "with no updates to the model parameters (inductive)? Note: here, inductive implies "
510+ "zero-shot since a model that never uses eval data to adapt also does not use "
511+ "supervision on this task. Single-session models are always transductive, since they "
512+ "are trained from scratch on the eval session."
513+ ),
514+ "finetuning_strategy" : (
515+ "If finetuning was done from a pretrained model, what kind of strategy was used? "
516+ "Options include linear/MLP probing, gradual unfreezing, full finetuning, etc. For "
517+ "TS3, was a single-unit or multi-unit probe used? If a strategy not listed here was "
518+ "used, please select Other and describe it."
519+ ),
520+ }
521+
332522
333523class TaskScore (SQLModel , table = True ):
334524 """Mean ± SEM over seeds for one TaskSubmission.
0 commit comments