cjm-transcript-segment-align 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ __version__ = "0.0.1"
@@ -0,0 +1,101 @@
1
+ # Autogenerated by nbdev
2
+
3
+ d = { 'settings': { 'branch': 'main',
4
+ 'doc_baseurl': '/cjm-transcript-segment-align',
5
+ 'doc_host': 'https://cj-mills.github.io',
6
+ 'git_url': 'https://github.com/cj-mills/cjm-transcript-segment-align',
7
+ 'lib_path': 'cjm_transcript_segment_align'},
8
+ 'syms': { 'cjm_transcript_segment_align.components.handlers': { 'cjm_transcript_segment_align.components.handlers._find_session_id': ( 'components/handlers.html#_find_session_id',
9
+ 'cjm_transcript_segment_align/components/handlers.py'),
10
+ 'cjm_transcript_segment_align.components.handlers.create_align_init_chrome_wrapper': ( 'components/handlers.html#create_align_init_chrome_wrapper',
11
+ 'cjm_transcript_segment_align/components/handlers.py'),
12
+ 'cjm_transcript_segment_align.components.handlers.create_seg_init_chrome_wrapper': ( 'components/handlers.html#create_seg_init_chrome_wrapper',
13
+ 'cjm_transcript_segment_align/components/handlers.py'),
14
+ 'cjm_transcript_segment_align.components.handlers.wrap_align_mutation_handler': ( 'components/handlers.html#wrap_align_mutation_handler',
15
+ 'cjm_transcript_segment_align/components/handlers.py'),
16
+ 'cjm_transcript_segment_align.components.handlers.wrap_seg_mutation_handler': ( 'components/handlers.html#wrap_seg_mutation_handler',
17
+ 'cjm_transcript_segment_align/components/handlers.py')},
18
+ 'cjm_transcript_segment_align.components.helpers': { 'cjm_transcript_segment_align.components.helpers.check_alignment_ready': ( 'components/helpers.html#check_alignment_ready',
19
+ 'cjm_transcript_segment_align/components/helpers.py'),
20
+ 'cjm_transcript_segment_align.components.helpers.extract_alignment_state': ( 'components/helpers.html#extract_alignment_state',
21
+ 'cjm_transcript_segment_align/components/helpers.py'),
22
+ 'cjm_transcript_segment_align.components.helpers.extract_seg_state': ( 'components/helpers.html#extract_seg_state',
23
+ 'cjm_transcript_segment_align/components/helpers.py'),
24
+ 'cjm_transcript_segment_align.components.helpers.get_chunk_count': ( 'components/helpers.html#get_chunk_count',
25
+ 'cjm_transcript_segment_align/components/helpers.py'),
26
+ 'cjm_transcript_segment_align.components.helpers.get_segment_count': ( 'components/helpers.html#get_segment_count',
27
+ 'cjm_transcript_segment_align/components/helpers.py')},
28
+ 'cjm_transcript_segment_align.components.keyboard_config': { 'cjm_transcript_segment_align.components.keyboard_config.build_combined_kb_system': ( 'components/keyboard_config.html#build_combined_kb_system',
29
+ 'cjm_transcript_segment_align/components/keyboard_config.py'),
30
+ 'cjm_transcript_segment_align.components.keyboard_config.generate_zone_change_js': ( 'components/keyboard_config.html#generate_zone_change_js',
31
+ 'cjm_transcript_segment_align/components/keyboard_config.py'),
32
+ 'cjm_transcript_segment_align.components.keyboard_config.render_keyboard_hints_collapsible': ( 'components/keyboard_config.html#render_keyboard_hints_collapsible',
33
+ 'cjm_transcript_segment_align/components/keyboard_config.py')},
34
+ 'cjm_transcript_segment_align.components.step_renderer': { 'cjm_transcript_segment_align.components.step_renderer._placeholder': ( 'components/step_renderer.html#_placeholder',
35
+ 'cjm_transcript_segment_align/components/step_renderer.py'),
36
+ 'cjm_transcript_segment_align.components.step_renderer._render_alignment_column': ( 'components/step_renderer.html#_render_alignment_column',
37
+ 'cjm_transcript_segment_align/components/step_renderer.py'),
38
+ 'cjm_transcript_segment_align.components.step_renderer._render_column_header': ( 'components/step_renderer.html#_render_column_header',
39
+ 'cjm_transcript_segment_align/components/step_renderer.py'),
40
+ 'cjm_transcript_segment_align.components.step_renderer._render_keyboard_system_container': ( 'components/step_renderer.html#_render_keyboard_system_container',
41
+ 'cjm_transcript_segment_align/components/step_renderer.py'),
42
+ 'cjm_transcript_segment_align.components.step_renderer._render_seg_column': ( 'components/step_renderer.html#_render_seg_column',
43
+ 'cjm_transcript_segment_align/components/step_renderer.py'),
44
+ 'cjm_transcript_segment_align.components.step_renderer._render_shared_chrome': ( 'components/step_renderer.html#_render_shared_chrome',
45
+ 'cjm_transcript_segment_align/components/step_renderer.py'),
46
+ 'cjm_transcript_segment_align.components.step_renderer.render_align_mini_stats_badge': ( 'components/step_renderer.html#render_align_mini_stats_badge',
47
+ 'cjm_transcript_segment_align/components/step_renderer.py'),
48
+ 'cjm_transcript_segment_align.components.step_renderer.render_alignment_status': ( 'components/step_renderer.html#render_alignment_status',
49
+ 'cjm_transcript_segment_align/components/step_renderer.py'),
50
+ 'cjm_transcript_segment_align.components.step_renderer.render_alignment_status_text': ( 'components/step_renderer.html#render_alignment_status_text',
51
+ 'cjm_transcript_segment_align/components/step_renderer.py'),
52
+ 'cjm_transcript_segment_align.components.step_renderer.render_combined_step': ( 'components/step_renderer.html#render_combined_step',
53
+ 'cjm_transcript_segment_align/components/step_renderer.py'),
54
+ 'cjm_transcript_segment_align.components.step_renderer.render_footer_inner_content': ( 'components/step_renderer.html#render_footer_inner_content',
55
+ 'cjm_transcript_segment_align/components/step_renderer.py'),
56
+ 'cjm_transcript_segment_align.components.step_renderer.render_seg_mini_stats_badge': ( 'components/step_renderer.html#render_seg_mini_stats_badge',
57
+ 'cjm_transcript_segment_align/components/step_renderer.py')},
58
+ 'cjm_transcript_segment_align.html_ids': { 'cjm_transcript_segment_align.html_ids.CombinedHtmlIds': ( 'html_ids.html#combinedhtmlids',
59
+ 'cjm_transcript_segment_align/html_ids.py'),
60
+ 'cjm_transcript_segment_align.html_ids.CombinedHtmlIds.as_selector': ( 'html_ids.html#combinedhtmlids.as_selector',
61
+ 'cjm_transcript_segment_align/html_ids.py')},
62
+ 'cjm_transcript_segment_align.routes.chrome': { 'cjm_transcript_segment_align.routes.chrome._handle_switch_chrome': ( 'routes/chrome.html#_handle_switch_chrome',
63
+ 'cjm_transcript_segment_align/routes/chrome.py'),
64
+ 'cjm_transcript_segment_align.routes.chrome.init_chrome_router': ( 'routes/chrome.html#init_chrome_router',
65
+ 'cjm_transcript_segment_align/routes/chrome.py')},
66
+ 'cjm_transcript_segment_align.routes.forced_alignment': { 'cjm_transcript_segment_align.routes.forced_alignment._handle_fa_toggle': ( 'routes/forced_alignment.html#_handle_fa_toggle',
67
+ 'cjm_transcript_segment_align/routes/forced_alignment.py'),
68
+ 'cjm_transcript_segment_align.routes.forced_alignment._handle_fa_trigger': ( 'routes/forced_alignment.html#_handle_fa_trigger',
69
+ 'cjm_transcript_segment_align/routes/forced_alignment.py'),
70
+ 'cjm_transcript_segment_align.routes.forced_alignment.init_forced_alignment_routers': ( 'routes/forced_alignment.html#init_forced_alignment_routers',
71
+ 'cjm_transcript_segment_align/routes/forced_alignment.py'),
72
+ 'cjm_transcript_segment_align.routes.forced_alignment.render_fa_controls': ( 'routes/forced_alignment.html#render_fa_controls',
73
+ 'cjm_transcript_segment_align/routes/forced_alignment.py'),
74
+ 'cjm_transcript_segment_align.routes.forced_alignment.render_fa_progress': ( 'routes/forced_alignment.html#render_fa_progress',
75
+ 'cjm_transcript_segment_align/routes/forced_alignment.py'),
76
+ 'cjm_transcript_segment_align.routes.forced_alignment.render_fa_toggle': ( 'routes/forced_alignment.html#render_fa_toggle',
77
+ 'cjm_transcript_segment_align/routes/forced_alignment.py'),
78
+ 'cjm_transcript_segment_align.routes.forced_alignment.render_fa_trigger_button': ( 'routes/forced_alignment.html#render_fa_trigger_button',
79
+ 'cjm_transcript_segment_align/routes/forced_alignment.py')},
80
+ 'cjm_transcript_segment_align.services.forced_alignment': { 'cjm_transcript_segment_align.services.forced_alignment.ForcedAlignmentService': ( 'services/forced_alignment.html#forcedalignmentservice',
81
+ 'cjm_transcript_segment_align/services/forced_alignment.py'),
82
+ 'cjm_transcript_segment_align.services.forced_alignment.ForcedAlignmentService.__init__': ( 'services/forced_alignment.html#forcedalignmentservice.__init__',
83
+ 'cjm_transcript_segment_align/services/forced_alignment.py'),
84
+ 'cjm_transcript_segment_align.services.forced_alignment.ForcedAlignmentService.align_and_split': ( 'services/forced_alignment.html#forcedalignmentservice.align_and_split',
85
+ 'cjm_transcript_segment_align/services/forced_alignment.py'),
86
+ 'cjm_transcript_segment_align.services.forced_alignment.ForcedAlignmentService.align_and_split_async': ( 'services/forced_alignment.html#forcedalignmentservice.align_and_split_async',
87
+ 'cjm_transcript_segment_align/services/forced_alignment.py'),
88
+ 'cjm_transcript_segment_align.services.forced_alignment.ForcedAlignmentService.align_and_split_combined_async': ( 'services/forced_alignment.html#forcedalignmentservice.align_and_split_combined_async',
89
+ 'cjm_transcript_segment_align/services/forced_alignment.py'),
90
+ 'cjm_transcript_segment_align.services.forced_alignment.ForcedAlignmentService.ensure_loaded': ( 'services/forced_alignment.html#forcedalignmentservice.ensure_loaded',
91
+ 'cjm_transcript_segment_align/services/forced_alignment.py'),
92
+ 'cjm_transcript_segment_align.services.forced_alignment.ForcedAlignmentService.is_available': ( 'services/forced_alignment.html#forcedalignmentservice.is_available',
93
+ 'cjm_transcript_segment_align/services/forced_alignment.py'),
94
+ 'cjm_transcript_segment_align.services.forced_alignment._strip_punct': ( 'services/forced_alignment.html#_strip_punct',
95
+ 'cjm_transcript_segment_align/services/forced_alignment.py'),
96
+ 'cjm_transcript_segment_align.services.forced_alignment.assign_words_to_chunks': ( 'services/forced_alignment.html#assign_words_to_chunks',
97
+ 'cjm_transcript_segment_align/services/forced_alignment.py'),
98
+ 'cjm_transcript_segment_align.services.forced_alignment.build_segments_from_alignment': ( 'services/forced_alignment.html#build_segments_from_alignment',
99
+ 'cjm_transcript_segment_align/services/forced_alignment.py'),
100
+ 'cjm_transcript_segment_align.services.forced_alignment.map_fa_words_to_text': ( 'services/forced_alignment.html#map_fa_words_to_text',
101
+ 'cjm_transcript_segment_align/services/forced_alignment.py')}}}
File without changes
@@ -0,0 +1,331 @@
1
+ """Handler wrappers for cross-domain coordination (alignment status updates)"""
2
+
3
+ # AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbs/components/handlers.ipynb.
4
+
5
+ # %% auto #0
6
+ __all__ = ['wrapped_seg_split', 'wrapped_seg_merge', 'wrapped_seg_undo', 'wrapped_seg_reset', 'wrapped_seg_ai_split',
7
+ 'wrap_seg_mutation_handler', 'wrap_align_mutation_handler', 'create_seg_init_chrome_wrapper',
8
+ 'create_align_init_chrome_wrapper']
9
+
10
+ # %% ../../nbs/components/handlers.ipynb #c3d4e5f6
11
+ import asyncio
12
+ from functools import wraps
13
+ from typing import Callable, Any, List
14
+
15
+ from fasthtml.common import Div, Span, Button
16
+
17
+ from cjm_fasthtml_interactions.core.state_store import get_session_id
18
+ from cjm_fasthtml_card_stack.components.controls import render_width_slider
19
+ from cjm_fasthtml_daisyui.components.data_display.badge import badge, badge_styles, badge_sizes
20
+ from cjm_fasthtml_tailwind.utilities.layout import display_tw
21
+ from cjm_fasthtml_tailwind.core.base import combine_classes
22
+
23
+ from ..html_ids import CombinedHtmlIds
24
+ from cjm_transcript_segment_align.components.step_renderer import (
25
+ render_alignment_status, render_seg_mini_stats_badge, render_align_mini_stats_badge,
26
+ render_footer_inner_content,
27
+ )
28
+ from cjm_transcript_segment_align.components.keyboard_config import (
29
+ build_combined_kb_system, render_keyboard_hints_collapsible,
30
+ generate_zone_change_js, SWITCH_CHROME_BTN_ID,
31
+ )
32
+ from cjm_transcript_segmentation.models import TextSegment, SegmentationUrls
33
+ from cjm_transcript_segmentation.routes.core import WorkflowStateStore
34
+ from cjm_transcript_segmentation.routes.handlers import (
35
+ SegInitResult, _handle_seg_init, _handle_seg_split, _handle_seg_merge,
36
+ _handle_seg_undo, _handle_seg_reset, _handle_seg_ai_split,
37
+ )
38
+ from cjm_transcript_segmentation.components.step_renderer import (
39
+ render_toolbar, render_seg_footer_content,
40
+ )
41
+ from cjm_transcript_segmentation.components.card_stack_config import (
42
+ SEG_CS_CONFIG, SEG_CS_IDS,
43
+ )
44
+ from cjm_transcript_vad_align.models import AlignmentUrls, VADChunk
45
+ from cjm_transcript_vad_align.routes.core import (
46
+ WorkflowStateStore as AlignWorkflowStateStore,
47
+ )
48
+ from cjm_transcript_vad_align.routes.handlers import (
49
+ AlignInitResult, _handle_align_init,
50
+ )
51
+ from cjm_transcript_vad_align.services.alignment import AlignmentService
52
+ from cjm_transcript_source_select.services.source import SourceService
53
+
54
+ # %% ../../nbs/components/handlers.ipynb #e5f6a7b8
55
+ def _find_session_id(args, kwargs):
56
+ """Find session_id from args or kwargs."""
57
+ # First check kwargs
58
+ if 'sess' in kwargs:
59
+ try:
60
+ return get_session_id(kwargs['sess'])
61
+ except:
62
+ pass
63
+
64
+ # Search args - session objects have a 'session' key
65
+ for arg in args:
66
+ try:
67
+ session_id = get_session_id(arg)
68
+ if session_id:
69
+ return session_id
70
+ except:
71
+ continue
72
+
73
+ return None
74
+
75
+
76
+ def wrap_seg_mutation_handler(
77
+ handler: Callable, # Handler function to wrap
78
+ ) -> Callable: # Wrapped handler that appends alignment status OOB
79
+ """Wrap a segmentation mutation handler to add alignment status OOB.
80
+
81
+ The handler is expected to take (state_store, workflow_id, ...) as first params.
82
+ """
83
+ @wraps(handler)
84
+ async def wrapped(
85
+ state_store: WorkflowStateStore,
86
+ workflow_id: str,
87
+ *args,
88
+ **kwargs
89
+ ):
90
+ # Call the original handler (async or sync)
91
+ if asyncio.iscoroutinefunction(handler):
92
+ result = await handler(state_store, workflow_id, *args, **kwargs)
93
+ else:
94
+ result = handler(state_store, workflow_id, *args, **kwargs)
95
+
96
+ # Find session_id from args/kwargs
97
+ session_id = _find_session_id(args, kwargs)
98
+
99
+ if session_id is not None:
100
+ # Get counts from state for alignment status
101
+ workflow_state = state_store.get_state(workflow_id, session_id)
102
+ step_states = workflow_state.get("step_states", {})
103
+ segment_count = len(step_states.get("segmentation", {}).get("segments", []))
104
+ chunk_count = len(step_states.get("alignment", {}).get("vad_chunks", []))
105
+
106
+ # Append alignment status OOB to result
107
+ return (*result, render_alignment_status(segment_count, chunk_count, oob=True))
108
+
109
+ # If we couldn't find session_id, just return the result without the status update
110
+ return result
111
+
112
+ return wrapped
113
+
114
+ # %% ../../nbs/components/handlers.ipynb #f6a7b8c9
115
+ def wrap_align_mutation_handler(
116
+ handler: Callable, # Handler function to wrap
117
+ ) -> Callable: # Wrapped handler that appends alignment status OOB
118
+ """Wrap an alignment mutation handler to add alignment status OOB.
119
+
120
+ The handler is expected to take (state_store, workflow_id, ...) as first params.
121
+ """
122
+ @wraps(handler)
123
+ async def wrapped(
124
+ state_store: WorkflowStateStore,
125
+ workflow_id: str,
126
+ *args,
127
+ **kwargs
128
+ ):
129
+ # Call the original handler (async or sync)
130
+ if asyncio.iscoroutinefunction(handler):
131
+ result = await handler(state_store, workflow_id, *args, **kwargs)
132
+ else:
133
+ result = handler(state_store, workflow_id, *args, **kwargs)
134
+
135
+ # Find session_id from args/kwargs
136
+ session_id = _find_session_id(args, kwargs)
137
+
138
+ if session_id is not None:
139
+ # Get counts from state for alignment status
140
+ workflow_state = state_store.get_state(workflow_id, session_id)
141
+ step_states = workflow_state.get("step_states", {})
142
+ segment_count = len(step_states.get("segmentation", {}).get("segments", []))
143
+ chunk_count = len(step_states.get("alignment", {}).get("vad_chunks", []))
144
+
145
+ # Append alignment status OOB to result
146
+ return (*result, render_alignment_status(segment_count, chunk_count, oob=True))
147
+
148
+ # If we couldn't find session_id, just return the result without the status update
149
+ return result
150
+
151
+ return wrapped
152
+
153
+ # %% ../../nbs/components/handlers.ipynb #rudnoqfwqe
154
+ def create_seg_init_chrome_wrapper(
155
+ align_urls:AlignmentUrls, # URL bundle for alignment routes (for KB system)
156
+ switch_chrome_url:str, # URL for chrome switching (for KB system)
157
+ fa_trigger_url:str="", # URL for forced alignment trigger (optional)
158
+ fa_toggle_url:str="", # URL for forced alignment toggle (optional)
159
+ fa_available:bool=False, # Whether forced alignment plugin is available
160
+ ) -> Callable: # Wrapped handler that builds KB system and shared chrome
161
+ """Create a wrapper for seg init that builds combined KB system and shared chrome.
162
+
163
+ This is a factory that captures the URLs needed for KB system assembly.
164
+ Optionally includes forced alignment controls if FA plugin is available.
165
+ """
166
+ # Import here to avoid circular imports (routes module imports from components)
167
+ from cjm_transcript_segment_align.routes.forced_alignment import render_fa_controls
168
+
169
+ async def wrapped_seg_init(
170
+ state_store:WorkflowStateStore,
171
+ workflow_id:str,
172
+ source_service:SourceService,
173
+ segmentation_service:Any,
174
+ request:Any,
175
+ sess:Any,
176
+ urls:SegmentationUrls,
177
+ visible_count:int=5,
178
+ card_width:int=40,
179
+ ):
180
+ """Wrapped seg init that adds KB system and shared chrome."""
181
+ # Call pure domain handler
182
+ result: SegInitResult = await _handle_seg_init(
183
+ state_store, workflow_id, source_service, segmentation_service,
184
+ request, sess, urls, visible_count, card_width,
185
+ )
186
+
187
+ session_id = get_session_id(sess)
188
+
189
+ # Get VAD chunk count for alignment status
190
+ workflow_state = state_store.get_state(workflow_id, session_id)
191
+ seg_state = workflow_state.get("step_states", {}).get("segmentation", {})
192
+ chunk_count = len(workflow_state.get("step_states", {}).get("alignment", {}).get("vad_chunks", []))
193
+ segment_count = len(result.segments)
194
+
195
+ # Build combined KB system with both zones
196
+ kb_manager, kb_system = build_combined_kb_system(urls, align_urls)
197
+
198
+ # OOB swap for stable keyboard system container
199
+ kb_system_oob = Div(
200
+ kb_system.script,
201
+ kb_system.hidden_inputs,
202
+ kb_system.action_buttons,
203
+ id=CombinedHtmlIds.KEYBOARD_SYSTEM,
204
+ hx_swap_oob="innerHTML"
205
+ )
206
+
207
+ # Zone change JS (goes in response for browser to execute)
208
+ zone_change_js = generate_zone_change_js(switch_chrome_url)
209
+
210
+ # Hidden chrome switch button for HTMX trigger
211
+ chrome_switch_btn = Button(
212
+ id=SWITCH_CHROME_BTN_ID,
213
+ cls=str(display_tw.hidden),
214
+ hx_post=switch_chrome_url,
215
+ hx_include=f"#{CombinedHtmlIds.ACTIVE_COLUMN_INPUT}",
216
+ hx_swap="none",
217
+ hx_swap_oob="true",
218
+ )
219
+
220
+ # Update hints to include zone switch info
221
+ hints_oob = Div(
222
+ render_keyboard_hints_collapsible(kb_manager, include_zone_switch=True),
223
+ id=CombinedHtmlIds.SHARED_HINTS,
224
+ hx_swap_oob="innerHTML"
225
+ )
226
+
227
+ # Toolbar OOB
228
+ toolbar_oob = Div(
229
+ render_toolbar(
230
+ reset_url=urls.reset, ai_split_url=urls.ai_split, undo_url=urls.undo,
231
+ can_undo=(result.history_depth > 0),
232
+ visible_count=result.visible_count,
233
+ is_auto_mode=result.is_auto_mode,
234
+ ),
235
+ id=CombinedHtmlIds.SHARED_TOOLBAR,
236
+ hx_swap_oob="innerHTML"
237
+ )
238
+
239
+ # Controls OOB (width slider)
240
+ controls_oob = Div(
241
+ render_width_slider(SEG_CS_CONFIG, SEG_CS_IDS, card_width=result.card_width),
242
+ id=CombinedHtmlIds.SHARED_CONTROLS,
243
+ hx_swap_oob="innerHTML"
244
+ )
245
+
246
+ # Footer OOB with alignment status
247
+ footer_oob = Div(
248
+ render_footer_inner_content(
249
+ render_seg_footer_content(result.segments, result.focused_index),
250
+ segment_count, chunk_count
251
+ ),
252
+ id=CombinedHtmlIds.SHARED_FOOTER,
253
+ hx_swap_oob="innerHTML"
254
+ )
255
+
256
+ # Mini-stats badge OOB
257
+ mini_stats_oob = render_seg_mini_stats_badge(result.segments, oob=True)
258
+
259
+ # FA controls OOB (trigger button or toggle based on state)
260
+ active_presplit = seg_state.get("active_presplit")
261
+ fa_controls_oob = render_fa_controls(
262
+ trigger_url=fa_trigger_url,
263
+ toggle_url=fa_toggle_url,
264
+ active_presplit=active_presplit,
265
+ fa_available=fa_available,
266
+ oob=True,
267
+ )
268
+
269
+ return (
270
+ result.column_body, kb_system_oob, zone_change_js, chrome_switch_btn,
271
+ hints_oob, toolbar_oob, controls_oob, footer_oob, mini_stats_oob,
272
+ fa_controls_oob,
273
+ )
274
+
275
+ return wrapped_seg_init
276
+
277
+ # %% ../../nbs/components/handlers.ipynb #ecvyiypdxk
278
+ def create_align_init_chrome_wrapper() -> Callable: # Wrapped handler that adds alignment status
279
+ """Create a wrapper for align init that adds mini-stats and alignment status.
280
+
281
+ Alignment init is simpler than seg init - it doesn't need to build the
282
+ full KB system (seg init handles that). It just updates alignment-specific
283
+ chrome and the alignment status badge.
284
+ """
285
+ async def wrapped_align_init(
286
+ state_store:WorkflowStateStore,
287
+ workflow_id:str,
288
+ source_service:SourceService,
289
+ alignment_service:AlignmentService,
290
+ request:Any,
291
+ sess:Any,
292
+ urls:AlignmentUrls,
293
+ visible_count:int=5,
294
+ card_width:int=40,
295
+ ):
296
+ """Wrapped align init that adds mini-stats and alignment status."""
297
+ # Call pure domain handler
298
+ result: AlignInitResult = await _handle_align_init(
299
+ state_store, workflow_id, source_service, alignment_service,
300
+ request, sess, urls, visible_count, card_width,
301
+ )
302
+
303
+ session_id = get_session_id(sess)
304
+
305
+ # Get segment count for alignment status
306
+ workflow_state = state_store.get_state(workflow_id, session_id)
307
+ segment_count = len(workflow_state.get("step_states", {}).get("segmentation", {}).get("segments", []))
308
+ chunk_count = len(result.chunks)
309
+
310
+ # Mini-stats badge OOB
311
+ mini_stats_oob = render_align_mini_stats_badge(result.chunks, oob=True)
312
+
313
+ # Alignment status OOB
314
+ alignment_status_oob = render_alignment_status(segment_count, chunk_count, oob=True)
315
+
316
+ return (result.column_body, mini_stats_oob, alignment_status_oob)
317
+
318
+ return wrapped_align_init
319
+
320
+ # %% ../../nbs/components/handlers.ipynb #b8c9d0e1
321
+ # Segmentation mutation handlers (change segment count, but NOT init)
322
+ # These add alignment status OOB but don't build KB system or shared chrome.
323
+ # Init handlers use the factory wrappers (create_seg_init_chrome_wrapper, etc.)
324
+ wrapped_seg_split = wrap_seg_mutation_handler(_handle_seg_split)
325
+ wrapped_seg_merge = wrap_seg_mutation_handler(_handle_seg_merge)
326
+ wrapped_seg_undo = wrap_seg_mutation_handler(_handle_seg_undo)
327
+ wrapped_seg_reset = wrap_seg_mutation_handler(_handle_seg_reset)
328
+ wrapped_seg_ai_split = wrap_seg_mutation_handler(_handle_seg_ai_split)
329
+
330
+ # Note: Alignment has no mutation handlers other than init.
331
+ # Alignment init uses create_align_init_chrome_wrapper() factory.
@@ -0,0 +1,85 @@
1
+ """State extraction helpers for cross-domain coordination in Phase 2 combined step"""
2
+
3
+ # AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbs/components/helpers.ipynb.
4
+
5
+ # %% auto #0
6
+ __all__ = ['SEG_DEFAULT_VISIBLE_COUNT', 'SEG_DEFAULT_CARD_WIDTH', 'ALIGN_DEFAULT_VISIBLE_COUNT', 'ALIGN_DEFAULT_CARD_WIDTH',
7
+ 'check_alignment_ready', 'extract_seg_state', 'extract_alignment_state', 'get_segment_count',
8
+ 'get_chunk_count']
9
+
10
+ # %% ../../nbs/components/helpers.ipynb #c3d4e5f6
11
+ from typing import Any, Dict, List, Optional
12
+
13
+ from cjm_fasthtml_interactions.core.context import InteractionContext
14
+ from cjm_fasthtml_card_stack.core.constants import DEFAULT_VISIBLE_COUNT, DEFAULT_CARD_WIDTH
15
+
16
+ from cjm_transcript_segmentation.models import TextSegment
17
+ from cjm_transcript_vad_align.models import VADChunk
18
+
19
+ # %% ../../nbs/components/helpers.ipynb #e5f6a7b8
20
+ def check_alignment_ready(
21
+ segment_count:int, # Number of text segments
22
+ chunk_count:int, # Number of VAD chunks
23
+ ) -> bool: # True if counts match for 1:1 alignment
24
+ """Check if segment and VAD chunk counts match for 1:1 alignment."""
25
+ return segment_count > 0 and chunk_count > 0 and segment_count == chunk_count
26
+
27
+ # %% ../../nbs/components/helpers.ipynb #a7b8c9d0
28
+ # Default values for segmentation card stack (wider cards for text)
29
+ SEG_DEFAULT_VISIBLE_COUNT = 3
30
+ SEG_DEFAULT_CARD_WIDTH = 80
31
+
32
+ # %% ../../nbs/components/helpers.ipynb #b8c9d0e1
33
+ def extract_seg_state(
34
+ ctx:InteractionContext, # Interaction context with state
35
+ ) -> Dict[str, Any]: # Extracted state values
36
+ """Extract segmentation state as explicit values for renderers."""
37
+ state = ctx.state.get("step_states", {}).get("segmentation", {})
38
+ return {
39
+ "is_initialized": state.get("is_initialized", False),
40
+ "segments": [TextSegment.from_dict(s) for s in state.get("segments", [])],
41
+ "focused_index": state.get("focused_index", 0),
42
+ "visible_count": state.get("visible_count", SEG_DEFAULT_VISIBLE_COUNT),
43
+ "card_width": state.get("card_width", SEG_DEFAULT_CARD_WIDTH),
44
+ "history": state.get("history", []),
45
+ "is_auto_mode": state.get("is_auto_mode", False),
46
+ }
47
+
48
+ # %% ../../nbs/components/helpers.ipynb #d0e1f2a3
49
+ # Default values for alignment card stack (compact cards for VAD chunks)
50
+ ALIGN_DEFAULT_VISIBLE_COUNT = 5
51
+ ALIGN_DEFAULT_CARD_WIDTH = 40
52
+
53
+ # %% ../../nbs/components/helpers.ipynb #e1f2a3b4
54
+ def extract_alignment_state(
55
+ ctx:InteractionContext, # Interaction context with state
56
+ ) -> Dict[str, Any]: # Extracted state values
57
+ """Extract alignment state as explicit values for renderers."""
58
+ state = ctx.state.get("step_states", {}).get("alignment", {})
59
+ return {
60
+ "is_initialized": state.get("is_initialized", False),
61
+ "vad_chunks": [VADChunk.from_dict(c) for c in state.get("vad_chunks", [])],
62
+ "focused_index": state.get("focused_chunk_index", 0),
63
+ "visible_count": state.get("visible_count", ALIGN_DEFAULT_VISIBLE_COUNT),
64
+ "card_width": state.get("card_width", ALIGN_DEFAULT_CARD_WIDTH),
65
+ "media_path": state.get("media_path"),
66
+ "media_paths": state.get("media_paths", []),
67
+ "audio_duration": state.get("audio_duration"),
68
+ "is_auto_mode": state.get("is_auto_mode", False),
69
+ }
70
+
71
+ # %% ../../nbs/components/helpers.ipynb #a3b4c5d6
72
+ def get_segment_count(
73
+ ctx:InteractionContext, # Interaction context with state
74
+ ) -> int: # Number of segments
75
+ """Get segment count from state without full extraction."""
76
+ state = ctx.state.get("step_states", {}).get("segmentation", {})
77
+ return len(state.get("segments", []))
78
+
79
+
80
+ def get_chunk_count(
81
+ ctx:InteractionContext, # Interaction context with state
82
+ ) -> int: # Number of VAD chunks
83
+ """Get VAD chunk count from state without full extraction."""
84
+ state = ctx.state.get("step_states", {}).get("alignment", {})
85
+ return len(state.get("vad_chunks", []))