1010
1111import numpy as np
1212import pandas as pd
13+ from aind_behavior_dynamic_foraging .task_logic import AindDynamicForagingTaskLogic
14+ from aind_behavior_dynamic_foraging .task_logic .trial_generators import TrialGeneratorSpec
1315from aind_behavior_dynamic_foraging .task_logic .trial_models import Trial , TrialOutcome
1416from aind_behavior_services .task .distributions import Distribution , DistributionFamily
1517from contraqctor .contract import Dataset
@@ -274,7 +276,42 @@ def _auto_water(trial: t.Optional[Trial], *, side: bool) -> t.Optional[int]:
274276 # ------------------------------------------------------------------ #
275277 # Session-level (constant across trials) columns
276278 # ------------------------------------------------------------------ #
277- def _session_columns (self , task_logic : t .Optional [t .Any ]) -> t .Dict [str , t .Any ]:
279+ @staticmethod
280+ def _coupled_generator (
281+ generator : TrialGeneratorSpec ,
282+ ) -> t .Optional [TrialGeneratorSpec ]:
283+ """Resolve the ``CoupledTrialGenerator`` carrying the session parameters.
284+
285+ ``task_parameters.trial_generator`` may be a single generator spec or a
286+ ``TrialGeneratorCompositeSpec`` wrapping several sub-generators in
287+ ``.generators``. The block-length, ITI, delay, and reward-probability
288+ summaries come from the coupled generator; this unwraps the composite
289+ and returns it, or returns ``generator`` unchanged when it is not a
290+ composite.
291+
292+ Parameters
293+ ----------
294+ generator : TrialGeneratorSpec
295+ The ``trial_generator`` from the task parameters.
296+
297+ Returns
298+ -------
299+ TrialGeneratorSpec or None
300+ The coupled generator, or ``None`` if a composite contains none.
301+ """
302+ sub_generators = getattr (generator , "generators" , None )
303+ if sub_generators is None :
304+ return generator
305+ for sub in sub_generators :
306+ if getattr (sub , "type" , None ) == "CoupledTrialGenerator" :
307+ return sub
308+ logger .warning (
309+ "No CoupledTrialGenerator among composite trial generators; "
310+ "session columns will be null."
311+ )
312+ return None
313+
314+ def _session_columns (self , task_logic : AindDynamicForagingTaskLogic ) -> t .Dict [str , t .Any ]:
278315 """Return the per-session trial columns derived from the task logic.
279316
280317 These (block/ITI/delay distribution summaries and reward structure) are
@@ -284,7 +321,9 @@ def _session_columns(self, task_logic: t.Optional[t.Any]) -> t.Dict[str, t.Any]:
284321 if task_logic is None :
285322 return columns
286323
287- generator = task_logic .task_parameters .trial_generator
324+ generator = self ._coupled_generator (task_logic .task_parameters .trial_generator )
325+ if generator is None :
326+ return columns
288327
289328 block_beta , block_min , block_max = self ._distribution_stats (generator .block_length )
290329 iti_beta , iti_min , iti_max = self ._distribution_stats (
0 commit comments