2727 ProcessorConfig ,
2828 ProcessorType ,
2929)
30+ from data_designer .config .terminal_failure import TerminalTaskFailure
3031from data_designer .config .utils .type_helpers import StrEnum
3132from data_designer .config .version import get_library_version
3233from data_designer .engine .column_generators .generators .base import (
@@ -193,6 +194,7 @@ def __init__(
193194 # async run, if any. Used by the interface to surface the original cause
194195 # when a run produces 0 records due to deterministic failures.
195196 self ._first_non_retryable_error : Exception | None = None
197+ self ._terminal_failures : list [TerminalTaskFailure ] = []
196198
197199 self ._data_designer_config = compile_data_designer_config (data_designer_config , resource_provider )
198200 self ._column_configs = compile_dataset_builder_column_configs (self ._data_designer_config )
@@ -239,6 +241,11 @@ def first_non_retryable_error(self) -> Exception | None:
239241 """First non-retryable error captured by the scheduler in the most recent run."""
240242 return self ._first_non_retryable_error
241243
244+ @property
245+ def terminal_failures (self ) -> list [TerminalTaskFailure ]:
246+ """Terminal column failures captured during the most recent run."""
247+ return list (self ._terminal_failures )
248+
242249 @functools .cached_property
243250 def single_column_configs (self ) -> list [ColumnConfigT ]:
244251 configs = []
@@ -256,6 +263,7 @@ def build(
256263 on_batch_complete : Callable [[Path ], None ] | None = None ,
257264 save_multimedia_to_disk : bool = True ,
258265 resume : ResumeMode = ResumeMode .NEVER ,
266+ capture_terminal_failures : bool = False ,
259267 ) -> Path :
260268 """Build the dataset.
261269
@@ -279,6 +287,7 @@ def build(
279287
280288 In all resume modes, in-flight partial results from the interrupted run are
281289 discarded before generation continues.
290+ capture_terminal_failures: Capture the terminal column for omitted seed rows.
282291
283292 Returns:
284293 Path to the generated dataset directory.
@@ -351,7 +360,14 @@ def build(
351360 resume = ResumeMode .NEVER
352361 self .artifact_storage .resume = ResumeMode .NEVER
353362
354- self ._build_async (generators , num_records , buffer_size , on_batch_complete , resume = resume )
363+ self ._build_async (
364+ generators ,
365+ num_records ,
366+ buffer_size ,
367+ on_batch_complete ,
368+ resume = resume ,
369+ capture_terminal_failures = capture_terminal_failures ,
370+ )
355371
356372 # After-generation processors run unconditionally on the on-disk dataset
357373 # (not gated on ``generated``). When resume sees every row group already
@@ -537,7 +553,7 @@ def _load_resume_state(self, num_records: int, buffer_size: int) -> _ResumeState
537553 completed_row_groups = completed_row_groups ,
538554 )
539555
540- def build_preview (self , * , num_records : int ) -> pd .DataFrame :
556+ def build_preview (self , * , num_records : int , capture_terminal_failures : bool = False ) -> pd .DataFrame :
541557 self ._reset_run_state ()
542558 run_readiness_check (
543559 self .single_column_configs ,
@@ -551,7 +567,11 @@ def build_preview(self, *, num_records: int) -> pd.DataFrame:
551567 generators , self ._graph = self ._initialize_generators_and_graph ()
552568 start_time = time .perf_counter ()
553569
554- dataset = self ._build_async_preview (generators , num_records )
570+ dataset = self ._build_async_preview (
571+ generators ,
572+ num_records ,
573+ capture_terminal_failures = capture_terminal_failures ,
574+ )
555575
556576 self ._resource_provider .model_registry .log_model_usage (time .perf_counter () - start_time )
557577
@@ -564,8 +584,15 @@ def _reset_run_state(self) -> None:
564584 self ._actual_num_records = - 1
565585 self ._first_non_retryable_error = None
566586 self ._task_traces = []
587+ self ._terminal_failures = []
567588
568- def _build_async_preview (self , generators : list [ColumnGenerator ], num_records : int ) -> pd .DataFrame :
589+ def _build_async_preview (
590+ self ,
591+ generators : list [ColumnGenerator ],
592+ num_records : int ,
593+ * ,
594+ capture_terminal_failures : bool = False ,
595+ ) -> pd .DataFrame :
569596 """Async preview path - single row group, no disk writes, returns in-memory DataFrame."""
570597 logger .info ("⚡ Using async task-queue preview" )
571598
@@ -578,6 +605,7 @@ def _build_async_preview(self, generators: list[ColumnGenerator], num_records: i
578605 buffer_size = num_records ,
579606 run_post_batch_in_scheduler = False ,
580607 trace = trace_enabled ,
608+ capture_terminal_failures = capture_terminal_failures ,
581609 )
582610
583611 loop = ensure_async_engine_loop ()
@@ -590,6 +618,7 @@ def _build_async_preview(self, generators: list[ColumnGenerator], num_records: i
590618 self ._partial_row_groups = scheduler .partial_row_groups
591619 self ._actual_num_records = buffer_manager .actual_num_records
592620 self ._first_non_retryable_error = scheduler .first_non_retryable_error
621+ self ._terminal_failures = scheduler .terminal_failures
593622
594623 if not buffer_manager .has_row_group (0 ):
595624 return lazy .pd .DataFrame ()
@@ -746,6 +775,7 @@ def _build_async(
746775 on_batch_complete : Callable [[Path ], None ] | None = None ,
747776 * ,
748777 resume : ResumeMode = ResumeMode .NEVER ,
778+ capture_terminal_failures : bool = False ,
749779 ) -> bool :
750780 """Async task-queue builder path - dispatches tasks based on dependency readiness.
751781
@@ -851,6 +881,7 @@ def on_complete(final_path: Path | str | None) -> None:
851881 initial_actual_num_records = initial_actual_num_records ,
852882 initial_total_num_batches = initial_total_num_batches ,
853883 scheduler_event_sink = scheduler_event_sink ,
884+ capture_terminal_failures = capture_terminal_failures ,
854885 )
855886
856887 # Run on background event loop. Capture scheduler state in `finally`
@@ -867,6 +898,7 @@ def on_complete(final_path: Path | str | None) -> None:
867898 self ._partial_row_groups = scheduler .partial_row_groups
868899 self ._actual_num_records = buffer_manager .actual_num_records
869900 self ._first_non_retryable_error = scheduler .first_non_retryable_error
901+ self ._terminal_failures = scheduler .terminal_failures
870902
871903 # Emit telemetry
872904 try :
@@ -917,6 +949,7 @@ def _prepare_async_run(
917949 initial_actual_num_records : int = 0 ,
918950 initial_total_num_batches : int = 0 ,
919951 scheduler_event_sink : SchedulerAdmissionEventSink | None = None ,
952+ capture_terminal_failures : bool = False ,
920953 ) -> tuple [AsyncTaskScheduler , RowGroupBufferManager ]:
921954 """Build a fully-wired scheduler and buffer manager for async generation.
922955
@@ -1008,6 +1041,7 @@ def on_before_checkpoint(rg_id: int, rg_size: int) -> None:
10081041 ),
10091042 request_pressure_provider = self ._resource_provider .model_registry .request_admission ,
10101043 request_pressure_advisory = True ,
1044+ capture_terminal_failures = capture_terminal_failures ,
10111045 )
10121046 return scheduler , buffer_manager
10131047
0 commit comments