cjm-transcript-correction-core 0.0.3__tar.gz → 0.0.5__tar.gz

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.
Files changed (30) hide show
  1. {cjm_transcript_correction_core-0.0.3/cjm_transcript_correction_core.egg-info → cjm_transcript_correction_core-0.0.5}/PKG-INFO +39 -7
  2. {cjm_transcript_correction_core-0.0.3 → cjm_transcript_correction_core-0.0.5}/README.md +35 -3
  3. cjm_transcript_correction_core-0.0.5/cjm_transcript_correction_core/__init__.py +1 -0
  4. {cjm_transcript_correction_core-0.0.3 → cjm_transcript_correction_core-0.0.5}/cjm_transcript_correction_core/cli.py +157 -8
  5. cjm_transcript_correction_core-0.0.5/cjm_transcript_correction_core/graph.py +1915 -0
  6. {cjm_transcript_correction_core-0.0.3 → cjm_transcript_correction_core-0.0.5}/cjm_transcript_correction_core/journal.py +31 -12
  7. {cjm_transcript_correction_core-0.0.3 → cjm_transcript_correction_core-0.0.5}/cjm_transcript_correction_core/models.py +75 -3
  8. {cjm_transcript_correction_core-0.0.3 → cjm_transcript_correction_core-0.0.5}/cjm_transcript_correction_core/pipeline.py +16 -7
  9. {cjm_transcript_correction_core-0.0.3 → cjm_transcript_correction_core-0.0.5}/cjm_transcript_correction_core/signals.py +52 -1
  10. {cjm_transcript_correction_core-0.0.3 → cjm_transcript_correction_core-0.0.5/cjm_transcript_correction_core.egg-info}/PKG-INFO +39 -7
  11. cjm_transcript_correction_core-0.0.5/cjm_transcript_correction_core.egg-info/entry_points.txt +5 -0
  12. cjm_transcript_correction_core-0.0.5/cjm_transcript_correction_core.egg-info/requires.txt +4 -0
  13. {cjm_transcript_correction_core-0.0.3 → cjm_transcript_correction_core-0.0.5}/pyproject.toml +4 -1
  14. cjm_transcript_correction_core-0.0.5/tests/test_graph.py +701 -0
  15. {cjm_transcript_correction_core-0.0.3 → cjm_transcript_correction_core-0.0.5}/tests/test_journal.py +15 -2
  16. {cjm_transcript_correction_core-0.0.3 → cjm_transcript_correction_core-0.0.5}/tests/test_models.py +33 -1
  17. {cjm_transcript_correction_core-0.0.3 → cjm_transcript_correction_core-0.0.5}/tests/test_signals.py +31 -0
  18. cjm_transcript_correction_core-0.0.3/cjm_transcript_correction_core/__init__.py +0 -1
  19. cjm_transcript_correction_core-0.0.3/cjm_transcript_correction_core/graph.py +0 -838
  20. cjm_transcript_correction_core-0.0.3/cjm_transcript_correction_core.egg-info/entry_points.txt +0 -2
  21. cjm_transcript_correction_core-0.0.3/cjm_transcript_correction_core.egg-info/requires.txt +0 -4
  22. cjm_transcript_correction_core-0.0.3/tests/test_graph.py +0 -180
  23. {cjm_transcript_correction_core-0.0.3 → cjm_transcript_correction_core-0.0.5}/LICENSE +0 -0
  24. {cjm_transcript_correction_core-0.0.3 → cjm_transcript_correction_core-0.0.5}/MANIFEST.in +0 -0
  25. {cjm_transcript_correction_core-0.0.3 → cjm_transcript_correction_core-0.0.5}/cjm_transcript_correction_core.egg-info/SOURCES.txt +0 -0
  26. {cjm_transcript_correction_core-0.0.3 → cjm_transcript_correction_core-0.0.5}/cjm_transcript_correction_core.egg-info/dependency_links.txt +0 -0
  27. {cjm_transcript_correction_core-0.0.3 → cjm_transcript_correction_core-0.0.5}/cjm_transcript_correction_core.egg-info/top_level.txt +0 -0
  28. {cjm_transcript_correction_core-0.0.3 → cjm_transcript_correction_core-0.0.5}/setup.cfg +0 -0
  29. {cjm_transcript_correction_core-0.0.3 → cjm_transcript_correction_core-0.0.5}/tests/test_cli.py +0 -0
  30. {cjm_transcript_correction_core-0.0.3 → cjm_transcript_correction_core-0.0.5}/tests/test_pipeline.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cjm-transcript-correction-core
3
- Version: 0.0.3
3
+ Version: 0.0.5
4
4
  Summary: A frontend-agnostic core for the transcript correction workflow — the first downstream graph-extension core; composes the graph-storage capability worker into a headless pipeline that applies unified, non-destructive corrections (text, punctuation, segmentation) as a supersede-able overlay on a committed decomposition spine, recomputes the review worklist from deterministic signals, and exposes a CLI as its first driver.
5
5
  Author-email: "Christian J. Mills" <9126128+cj-mills@users.noreply.github.com>
6
6
  License: Apache-2.0
@@ -10,9 +10,9 @@ Classifier: Programming Language :: Python :: 3 :: Only
10
10
  Requires-Python: >=3.12
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
- Requires-Dist: cjm_substrate>=0.0.51
14
- Requires-Dist: cjm_context_graph_primitives>=0.0.14
15
- Requires-Dist: cjm-context-graph-layer>=0.0.10
13
+ Requires-Dist: cjm_substrate>=0.0.57
14
+ Requires-Dist: cjm_context_graph_primitives>=0.0.15
15
+ Requires-Dist: cjm-context-graph-layer>=0.0.11
16
16
  Requires-Dist: cjm-transcript-graph-schema>=0.0.7
17
17
  Dynamic: license-file
18
18
 
@@ -40,37 +40,67 @@ A frontend-agnostic core for the transcript correction workflow — the first do
40
40
  - `main` _function_ — CLI entry point (console script: `cjm-transcript-correction-core`).
41
41
  - `review_command` _function_ — Execute the `review` subcommand: interactive text corrections over the flagged worklist.
42
42
  - `run_command` _function_ — Execute the `run` subcommand: correct a decomp manifest's committed spine.
43
+ - `stats_command` _function_ — Execute the `stats` subcommand: flywheel accounting over the shared graph.
43
44
 
44
45
  ### `cjm_transcript_correction_core.graph`
45
46
 
46
47
  - `active_corrections` _function_ — Filter to the effective correction set (the layer's resolve_active over a read superseded set).
48
+ - `active_speaker_assignments` _function_ — Project the ACTIVE speaker assignment per segment (latest-wins).
49
+ - `aggregate_session_purposes` _function_ — Fold sessions into a per-source purpose mix (pure; d915d545 picker rung).
50
+ - `apply_chunk_inserts` _function_ — Synthesize inserted chunks into the effective spine (DEC 3d3fa2a8).
51
+ - `apply_time_nudges` _function_ — Apply timing corrections onto segment times (latest-wins per edge).
47
52
  - `build_boundary_shift_correction` _function_ — Build a grouping Correction that moves text across one segment boundary.
53
+ - `build_chunk_insert_correction` _function_ — Build an insertion Correction that adds a chunk the skeleton never cut (DEC 3d3fa2a8).
54
+ - `build_chunk_split_corrections` _function_ — Compose a chunk SPLIT from the EXISTING verbs (work item 99c1d2ba) — no
48
55
  - `build_correction_node` _function_ — Construct a Correction overlay node (pure; commit happens separately).
56
+ - `build_mark_correction` _function_ — Build a NON-MUTATING mark Correction (DEC 2a231843: routed attention).
49
57
  - `build_prune_amendment` _function_ — Build a grouping Correction that supersedes a prune with a REDUCED set (unprune).
50
58
  - `build_prune_correction` _function_ — Build one batch grouping Correction that prunes empty segments (D14).
51
59
  - `build_reject_review` _function_ — Build a review Correction that REJECTS a prior correction (reject-as-supersede).
60
+ - `build_speaker_assign_correction` _function_ — Build a speaker Correction — the assignment op envelope (DEC d6df3a8e).
52
61
  - `build_text_correction` _function_ — Build a text_content Correction + its CORRECTS (+ optional SUPERSEDES) edges.
62
+ - `build_time_nudge_correction` _function_ — Build a timing Correction that nudges segment boundary TIMES (node + CORRECTS edges).
53
63
  - `commit_boundary_shift_correction` _function_ — Commit a boundary-shift correction (node + CORRECTS x2 [+ SUPERSEDES]) + REVIEWED markers on both segments.
64
+ - `commit_chunk_insert_correction` _function_ — Commit a chunk insertion (node + CORRECTS per flank).
65
+ - `commit_chunk_insert_removal` _function_ — Remove an inserted chunk WITHOUT touching layer-0 (reject-as-supersede).
66
+ - `commit_chunk_split_correction` _function_ — Commit a chunk split: three composed nodes in ONE atomic batch + ONE journal op.
67
+ - `commit_chunk_split_removal` _function_ — UNSPLIT: remove a split's right half AND its whole group (one review
68
+ - `commit_mark_correction` _function_ — Commit a mark (node + CORRECTS per anchored segment [+ SUPERSEDES]).
69
+ - `commit_mark_dismissal` _function_ — Dismiss an open mark WITHOUT a correction (reject-as-supersede).
54
70
  - `commit_nodes_edges` _function_ — Commit overlay nodes/edges through the layer's idempotent extend_graph.
55
71
  - `commit_prune_amendment` _function_ — Commit an unprune amendment (node + DERIVED_FROM edges + SUPERSEDES).
72
+ - `commit_speaker_assign_correction` _function_ — Commit a speaker assignment (node + CORRECTS per segment + ASSIGNS).
73
+ - `commit_speaker_entity` _function_ — Mint a source-spanning Entity into the shared registry (DEC 4ec6a49c).
56
74
  - `commit_text_correction` _function_ — Commit a text_content correction (node + CORRECTS [+ SUPERSEDES]) + a REVIEWED marker.
75
+ - `commit_time_nudge_correction` _function_ — Commit a time-nudge correction (node + CORRECTS per touched segment).
76
+ - `correction_stats` _function_ — Fold one Source's ACTIVE overlay into flywheel-accounting counts (pure).
57
77
  - `corrections_to_edits` _function_ — Map this core's Correction payloads onto the layer's spine-edit vocabulary.
58
- - `count_source_segments` _function_ — Count a Source's segments server-side under its chosen rendition (typed count mode).
78
+ - `count_source_segments` _function_ — Count a Source's segments server-side under its chosen rendition + skeleton (typed count mode).
59
79
  - `find_active_text_correction` _function_ — Single-segment convenience over the batch read (cross-session; latest wins).
60
80
  - `find_active_text_corrections_batch` _function_ — Active text corrections for MANY segments in TWO round-trips (C17).
81
+ - `find_chunk_split_group` _function_ — Resolve a split right-half's GROUP — the ac84360a group marker cashed in.
61
82
  - `find_corrections_for_session` _function_ — List corrections recorded in a session (typed property filter).
62
83
  - `find_prior_corrections_by_hash` _function_ — Cross-transcript correction-cache lookup (targeted; the graph IS the lexicon).
63
84
  - `get_session` _function_ — Fetch a CorrectionSession node by id (resume/reopen) — typed get, dict shape preserved.
85
+ - `list_source_spines` _function_ — The SPINES coexisting under a Source's chosen rendition (DEC f1024568).
86
+ - `list_speaker_entities` _function_ — Read the source-spanning entity registry (the picker's registry tier).
64
87
  - `load_empty_segments` _function_ — Load ONLY a Source's empty-text segments under its chosen rendition (D14 prune).
65
88
  - `load_review_markers` _function_ — Load a session's review markers (typed edge projection over REVIEWED edges).
66
89
  - `load_source_corrections` _function_ — Load every Correction targeting a Source (across sessions) + the superseded-id set.
67
- - `load_source_segments` _function_ — Load a Source's fine Segment spine under its chosen rendition (typed query surface).
90
+ - `load_source_segments` _function_ — Load a Source's fine Segment spine under its chosen rendition + skeleton (typed query surface).
68
91
  - `load_variant_texts` _function_ — Resolve per-transcriber chunk texts from the segments' CharSlice refs.
92
+ - `mark_anchor_segments` _function_ — Validate a mark anchor and list the Segment ids it touches.
93
+ - `open_marks` _function_ — Filter to the OPEN marks — the pass-2 worklist ('query open marks, walk them').
69
94
  - `project_effective_spine` _function_ — Project the effective spine = layer-0 + applied corrections.
95
+ - `reanchor_span` _function_ — Re-locate a span anchor in text that may have been edited since mark time.
70
96
  - `record_review_markers` _function_ — Persist per-(session, segment) review markers as REVIEWED edges.
97
+ - `rename_speaker_entity` _function_ — Rename an Entity on its STABLE id — identification IS a rename (DEC 484e2d74).
71
98
  - `resolve_source_renditions` _function_ — Pick the AudioRendition set whose fine Segment spine correction operates on.
99
+ - `session_purposes_by_source` _function_ — Read every CorrectionSession once and fold the per-source purpose mix.
100
+ - `set_session_purpose` _function_ — Update a session's purpose + updated_at (the test-session hygiene tag, DEC c86714a4).
72
101
  - `set_session_status` _function_ — Update a session's status + updated_at.
73
102
  - `source_audio_segment_ids` _function_ — The Source's coarse spine (one small typed read; ordered by index).
103
+ - `spine_where_for` _function_ — Resolve a skeleton selector against the observed spine set (pure).
74
104
  - `start_session` _function_ — Create + commit a new CorrectionSession node.
75
105
  - `submit_and_wait` _function_ — Submit one capability job, wait for it, return its result (raise on failure).
76
106
 
@@ -87,6 +117,7 @@ A frontend-agnostic core for the transcript correction workflow — the first do
87
117
  - `CorrectionManifest` _class_ — Durable record of one correction run (proto-bundle; chainable, CR-20).
88
118
  - `CorrectionRelations` _class_ — Registry of edge types the correction overlay adds to the spine graph.
89
119
  - `CorrectionSession` _class_ — A resumable, reopen-able correction review over one or more sources.
120
+ - `Entity` _class_ — A source-spanning identity in the shared entity substrate (DEC 4ec6a49c).
90
121
  - `SpineSegment` _class_ — A committed layer-0 Segment loaded from the graph (read view).
91
122
  - `WorklistItem` _class_ — One spine segment surfaced for review, with its deterministic Tier-1 flags.
92
123
  - `new_run_id` _function_ — Generate a unique, sortable correction run id.
@@ -112,9 +143,10 @@ A frontend-agnostic core for the transcript correction workflow — the first do
112
143
  - `fa_coverage_flags` _function_ — Flag segments whose forced-alignment coverage looks suspect (Tier-1).
113
144
  - `levenshtein` _function_ — Levenshtein edit distance (pure, in-core; variant-clustering primitive).
114
145
  - `phonetic_key` _function_ — Compute a coarse phonetic key for a word (groups like-sounding variants).
146
+ - `speaker_turn_proposals` _function_ — Dominant diarization cluster per segment — the assign lane's proposal paint.
115
147
  - `variant_divergence` _function_ — Within-segment cross-transcriber divergence (stage 5: intra-graph).
116
148
 
117
149
  ## Dependencies
118
150
 
119
151
  **Depends on:** `cjm-context-graph-layer`, `cjm-context-graph-primitives`, `cjm-substrate`, `cjm-transcript-graph-schema`
120
- **Used by:** `cjm-transcript-correction-tui`
152
+ **Used by:** `cjm-transcript-correction-tui`, `cjm-workflow-hub-tui`
@@ -22,37 +22,67 @@ A frontend-agnostic core for the transcript correction workflow — the first do
22
22
  - `main` _function_ — CLI entry point (console script: `cjm-transcript-correction-core`).
23
23
  - `review_command` _function_ — Execute the `review` subcommand: interactive text corrections over the flagged worklist.
24
24
  - `run_command` _function_ — Execute the `run` subcommand: correct a decomp manifest's committed spine.
25
+ - `stats_command` _function_ — Execute the `stats` subcommand: flywheel accounting over the shared graph.
25
26
 
26
27
  ### `cjm_transcript_correction_core.graph`
27
28
 
28
29
  - `active_corrections` _function_ — Filter to the effective correction set (the layer's resolve_active over a read superseded set).
30
+ - `active_speaker_assignments` _function_ — Project the ACTIVE speaker assignment per segment (latest-wins).
31
+ - `aggregate_session_purposes` _function_ — Fold sessions into a per-source purpose mix (pure; d915d545 picker rung).
32
+ - `apply_chunk_inserts` _function_ — Synthesize inserted chunks into the effective spine (DEC 3d3fa2a8).
33
+ - `apply_time_nudges` _function_ — Apply timing corrections onto segment times (latest-wins per edge).
29
34
  - `build_boundary_shift_correction` _function_ — Build a grouping Correction that moves text across one segment boundary.
35
+ - `build_chunk_insert_correction` _function_ — Build an insertion Correction that adds a chunk the skeleton never cut (DEC 3d3fa2a8).
36
+ - `build_chunk_split_corrections` _function_ — Compose a chunk SPLIT from the EXISTING verbs (work item 99c1d2ba) — no
30
37
  - `build_correction_node` _function_ — Construct a Correction overlay node (pure; commit happens separately).
38
+ - `build_mark_correction` _function_ — Build a NON-MUTATING mark Correction (DEC 2a231843: routed attention).
31
39
  - `build_prune_amendment` _function_ — Build a grouping Correction that supersedes a prune with a REDUCED set (unprune).
32
40
  - `build_prune_correction` _function_ — Build one batch grouping Correction that prunes empty segments (D14).
33
41
  - `build_reject_review` _function_ — Build a review Correction that REJECTS a prior correction (reject-as-supersede).
42
+ - `build_speaker_assign_correction` _function_ — Build a speaker Correction — the assignment op envelope (DEC d6df3a8e).
34
43
  - `build_text_correction` _function_ — Build a text_content Correction + its CORRECTS (+ optional SUPERSEDES) edges.
44
+ - `build_time_nudge_correction` _function_ — Build a timing Correction that nudges segment boundary TIMES (node + CORRECTS edges).
35
45
  - `commit_boundary_shift_correction` _function_ — Commit a boundary-shift correction (node + CORRECTS x2 [+ SUPERSEDES]) + REVIEWED markers on both segments.
46
+ - `commit_chunk_insert_correction` _function_ — Commit a chunk insertion (node + CORRECTS per flank).
47
+ - `commit_chunk_insert_removal` _function_ — Remove an inserted chunk WITHOUT touching layer-0 (reject-as-supersede).
48
+ - `commit_chunk_split_correction` _function_ — Commit a chunk split: three composed nodes in ONE atomic batch + ONE journal op.
49
+ - `commit_chunk_split_removal` _function_ — UNSPLIT: remove a split's right half AND its whole group (one review
50
+ - `commit_mark_correction` _function_ — Commit a mark (node + CORRECTS per anchored segment [+ SUPERSEDES]).
51
+ - `commit_mark_dismissal` _function_ — Dismiss an open mark WITHOUT a correction (reject-as-supersede).
36
52
  - `commit_nodes_edges` _function_ — Commit overlay nodes/edges through the layer's idempotent extend_graph.
37
53
  - `commit_prune_amendment` _function_ — Commit an unprune amendment (node + DERIVED_FROM edges + SUPERSEDES).
54
+ - `commit_speaker_assign_correction` _function_ — Commit a speaker assignment (node + CORRECTS per segment + ASSIGNS).
55
+ - `commit_speaker_entity` _function_ — Mint a source-spanning Entity into the shared registry (DEC 4ec6a49c).
38
56
  - `commit_text_correction` _function_ — Commit a text_content correction (node + CORRECTS [+ SUPERSEDES]) + a REVIEWED marker.
57
+ - `commit_time_nudge_correction` _function_ — Commit a time-nudge correction (node + CORRECTS per touched segment).
58
+ - `correction_stats` _function_ — Fold one Source's ACTIVE overlay into flywheel-accounting counts (pure).
39
59
  - `corrections_to_edits` _function_ — Map this core's Correction payloads onto the layer's spine-edit vocabulary.
40
- - `count_source_segments` _function_ — Count a Source's segments server-side under its chosen rendition (typed count mode).
60
+ - `count_source_segments` _function_ — Count a Source's segments server-side under its chosen rendition + skeleton (typed count mode).
41
61
  - `find_active_text_correction` _function_ — Single-segment convenience over the batch read (cross-session; latest wins).
42
62
  - `find_active_text_corrections_batch` _function_ — Active text corrections for MANY segments in TWO round-trips (C17).
63
+ - `find_chunk_split_group` _function_ — Resolve a split right-half's GROUP — the ac84360a group marker cashed in.
43
64
  - `find_corrections_for_session` _function_ — List corrections recorded in a session (typed property filter).
44
65
  - `find_prior_corrections_by_hash` _function_ — Cross-transcript correction-cache lookup (targeted; the graph IS the lexicon).
45
66
  - `get_session` _function_ — Fetch a CorrectionSession node by id (resume/reopen) — typed get, dict shape preserved.
67
+ - `list_source_spines` _function_ — The SPINES coexisting under a Source's chosen rendition (DEC f1024568).
68
+ - `list_speaker_entities` _function_ — Read the source-spanning entity registry (the picker's registry tier).
46
69
  - `load_empty_segments` _function_ — Load ONLY a Source's empty-text segments under its chosen rendition (D14 prune).
47
70
  - `load_review_markers` _function_ — Load a session's review markers (typed edge projection over REVIEWED edges).
48
71
  - `load_source_corrections` _function_ — Load every Correction targeting a Source (across sessions) + the superseded-id set.
49
- - `load_source_segments` _function_ — Load a Source's fine Segment spine under its chosen rendition (typed query surface).
72
+ - `load_source_segments` _function_ — Load a Source's fine Segment spine under its chosen rendition + skeleton (typed query surface).
50
73
  - `load_variant_texts` _function_ — Resolve per-transcriber chunk texts from the segments' CharSlice refs.
74
+ - `mark_anchor_segments` _function_ — Validate a mark anchor and list the Segment ids it touches.
75
+ - `open_marks` _function_ — Filter to the OPEN marks — the pass-2 worklist ('query open marks, walk them').
51
76
  - `project_effective_spine` _function_ — Project the effective spine = layer-0 + applied corrections.
77
+ - `reanchor_span` _function_ — Re-locate a span anchor in text that may have been edited since mark time.
52
78
  - `record_review_markers` _function_ — Persist per-(session, segment) review markers as REVIEWED edges.
79
+ - `rename_speaker_entity` _function_ — Rename an Entity on its STABLE id — identification IS a rename (DEC 484e2d74).
53
80
  - `resolve_source_renditions` _function_ — Pick the AudioRendition set whose fine Segment spine correction operates on.
81
+ - `session_purposes_by_source` _function_ — Read every CorrectionSession once and fold the per-source purpose mix.
82
+ - `set_session_purpose` _function_ — Update a session's purpose + updated_at (the test-session hygiene tag, DEC c86714a4).
54
83
  - `set_session_status` _function_ — Update a session's status + updated_at.
55
84
  - `source_audio_segment_ids` _function_ — The Source's coarse spine (one small typed read; ordered by index).
85
+ - `spine_where_for` _function_ — Resolve a skeleton selector against the observed spine set (pure).
56
86
  - `start_session` _function_ — Create + commit a new CorrectionSession node.
57
87
  - `submit_and_wait` _function_ — Submit one capability job, wait for it, return its result (raise on failure).
58
88
 
@@ -69,6 +99,7 @@ A frontend-agnostic core for the transcript correction workflow — the first do
69
99
  - `CorrectionManifest` _class_ — Durable record of one correction run (proto-bundle; chainable, CR-20).
70
100
  - `CorrectionRelations` _class_ — Registry of edge types the correction overlay adds to the spine graph.
71
101
  - `CorrectionSession` _class_ — A resumable, reopen-able correction review over one or more sources.
102
+ - `Entity` _class_ — A source-spanning identity in the shared entity substrate (DEC 4ec6a49c).
72
103
  - `SpineSegment` _class_ — A committed layer-0 Segment loaded from the graph (read view).
73
104
  - `WorklistItem` _class_ — One spine segment surfaced for review, with its deterministic Tier-1 flags.
74
105
  - `new_run_id` _function_ — Generate a unique, sortable correction run id.
@@ -94,9 +125,10 @@ A frontend-agnostic core for the transcript correction workflow — the first do
94
125
  - `fa_coverage_flags` _function_ — Flag segments whose forced-alignment coverage looks suspect (Tier-1).
95
126
  - `levenshtein` _function_ — Levenshtein edit distance (pure, in-core; variant-clustering primitive).
96
127
  - `phonetic_key` _function_ — Compute a coarse phonetic key for a word (groups like-sounding variants).
128
+ - `speaker_turn_proposals` _function_ — Dominant diarization cluster per segment — the assign lane's proposal paint.
97
129
  - `variant_divergence` _function_ — Within-segment cross-transcriber divergence (stage 5: intra-graph).
98
130
 
99
131
  ## Dependencies
100
132
 
101
133
  **Depends on:** `cjm-context-graph-layer`, `cjm-context-graph-primitives`, `cjm-substrate`, `cjm-transcript-graph-schema`
102
- **Used by:** `cjm-transcript-correction-tui`
134
+ **Used by:** `cjm-transcript-correction-tui`, `cjm-workflow-hub-tui`
@@ -3,11 +3,16 @@
3
3
  import argparse
4
4
  import asyncio
5
5
  import logging
6
+ import os
6
7
  from pathlib import Path
7
8
  from typing import Dict, List, Optional
8
9
 
10
+ from cjm_context_graph_layer.ops import graph_task
11
+ from cjm_context_graph_primitives.query import NodeQuery
9
12
  from cjm_substrate.core.manager import CapabilityManager
10
13
  from cjm_substrate.core.queue import JobQueue
14
+ from cjm_substrate.core.workspace import resolve_workspace
15
+ from cjm_transcript_correction_core.graph import correction_stats, load_source_corrections
11
16
  from cjm_transcript_correction_core.models import CorrectionConfig
12
17
  from cjm_transcript_correction_core.pipeline import (load_decomp_manifest, resolve_graph_db_path,
13
18
  run_correction, run_review)
@@ -18,7 +23,13 @@ logger = logging.getLogger(__name__)
18
23
  def _add_common_run_args(p: argparse.ArgumentParser) -> None: # Shared run/review arguments
19
24
  """Attach the capability / session / output arguments shared by `run` and `review`."""
20
25
  p.add_argument("manifest", help="Decomp-core run manifest JSON (the committed spine)")
21
- p.add_argument("--manifests-dir", default=".cjm/manifests", help="Capability manifests directory")
26
+ p.add_argument("--manifests-dir", default=None,
27
+ help="Capability manifests directory (default: the workspace's .cjm/manifests "
28
+ "when one is active, else .cjm/manifests under the cwd)")
29
+ p.add_argument("--workspace", default=None,
30
+ help="Workspace root (5daadfc4; default: CJM_WORKSPACE env, else upward walk "
31
+ "from cwd). Supplies manifests/output defaults and is exported so "
32
+ "capability workers resolve workspace-scoped paths")
22
33
  p.add_argument("--graph-capability", default="cjm-capability-graph-sqlite", help="Graph-storage capability name")
23
34
  p.add_argument("--graph-db-path", default=None,
24
35
  help="Override graph DB path (default: the decomp manifest's recorded db_path)")
@@ -26,12 +37,18 @@ def _add_common_run_args(p: argparse.ArgumentParser) -> None: # Shared run/revi
26
37
  help="Which AudioRendition spine to correct when a source has more than one "
27
38
  "(\"raw\" or a preprocessing substring e.g. \"demucs\"); default: auto-select the "
28
39
  "decomposed one (errors if ambiguous)")
40
+ p.add_argument("--skeleton", default=None,
41
+ help="Which SKELETON spine to correct when several coexist under one rendition "
42
+ "(sentence-split, DEC f1024568): \"legacy\" = the pre-split spine, or a "
43
+ "skeleton-hash prefix; default: auto (errors when >1 coexist)")
29
44
  p.add_argument("--sysmon-capability", default=None,
30
45
  help="monitor for empirical attribution (CR-7); loaded first; default: none")
31
46
  p.add_argument("--session", default=None, help="Resume an existing CorrectionSession id")
32
47
  p.add_argument("--reopen", action="store_true", help="Reopen a completed session (with --session)")
33
48
  p.add_argument("--actor", default="human", help="Actor recorded on corrections + review markers")
34
- p.add_argument("--output", default=None, help="Correction-manifest output path (default: runs/<run_id>.json)")
49
+ p.add_argument("--output", default=None,
50
+ help="Correction-manifest output path (default: <workspace>/runs/<run_id>.json "
51
+ "when a workspace is active, else runs/<run_id>.json under the cwd)")
35
52
  p.add_argument("-v", "--verbose", action="store_true", help="DEBUG-level logging")
36
53
 
37
54
 
@@ -56,6 +73,26 @@ def build_parser() -> argparse.ArgumentParser: # Configured CLI parser
56
73
  _add_common_run_args(review)
57
74
  review.add_argument("--review-max", type=int, default=0, help="Max worklist items to review (0 = all)")
58
75
  review.add_argument("-y", "--yes", action="store_true", help="Auto-mark every reviewed item (no edits)")
76
+
77
+ stats = sub.add_parser(
78
+ "stats", help="Flywheel accounting: active labeled spans / open marks / op counts per source")
79
+ stats.add_argument("--manifests-dir", default=None,
80
+ help="Capability manifests directory (default: the workspace's .cjm/manifests "
81
+ "when one is active, else .cjm/manifests under the cwd)")
82
+ stats.add_argument("--workspace", default=None,
83
+ help="Workspace root (default: CJM_WORKSPACE env, else upward walk from cwd)")
84
+ stats.add_argument("--graph-capability", default="cjm-capability-graph-sqlite",
85
+ help="Graph-storage capability name")
86
+ stats.add_argument("--graph-db-path", default=None,
87
+ help="Graph db path (default: the capability's persisted workspace config)")
88
+ stats.add_argument("--source", default=None,
89
+ help="Source node id or title substring (default: every Source)")
90
+ stats.add_argument("--label", default=None,
91
+ help="Spotlight one label/class (e.g. inhale): prints its insert+mark grand total")
92
+ stats.add_argument("--genuine-only", action="store_true",
93
+ help="Count only corrections from GENUINE sessions (purpose unset/genuine — "
94
+ "feature-test noise excluded, DEC c86714a4); supersession stays global")
95
+ stats.add_argument("-v", "--verbose", action="store_true", help="DEBUG-level logging")
59
96
  return parser
60
97
 
61
98
 
@@ -84,6 +121,14 @@ async def run_command(
84
121
  args: argparse.Namespace, # Parsed args for the `run` subcommand
85
122
  ) -> int: # Process exit code
86
123
  """Execute the `run` subcommand: correct a decomp manifest's committed spine."""
124
+ # 5daadfc4 workspace: resolve BEFORE any substrate config loads; export so
125
+ # the process tree (substrate config, capability workers) is workspace-scoped.
126
+ ws = resolve_workspace(explicit=getattr(args, "workspace", None))
127
+ if ws is not None:
128
+ os.environ["CJM_WORKSPACE"] = str(ws.root)
129
+ if args.manifests_dir is None:
130
+ args.manifests_dir = (str(ws.substrate_data_dir / "manifests")
131
+ if ws is not None else ".cjm/manifests")
87
132
  manifest_path = str(Path(args.manifest).resolve())
88
133
  if not Path(manifest_path).exists():
89
134
  raise SystemExit(f"decomp manifest not found: {manifest_path}")
@@ -96,7 +141,7 @@ async def run_command(
96
141
  cfg = CorrectionConfig(
97
142
  graph_capability=args.graph_capability, graph_db_path=graph_db_path,
98
143
  actor=args.actor, assume_yes=args.yes, prune_empty=not args.no_prune,
99
- rendition_selector=args.rendition,
144
+ rendition_selector=args.rendition, skeleton_selector=args.skeleton,
100
145
  )
101
146
 
102
147
  manager = CapabilityManager(
@@ -122,8 +167,9 @@ async def run_command(
122
167
  except Exception as e: # Best-effort teardown; never mask the run's outcome
123
168
  logger.warning(f"unload {iid} failed: {e}")
124
169
 
125
- out = Path(args.output) if args.output else Path("runs") / f"{manifest.run_id}.json"
126
- manifest.save(out)
170
+ out = (Path(args.output) if args.output
171
+ else (ws.runs_dir if ws is not None else Path("runs")) / f"{manifest.run_id}.json")
172
+ manifest.save(out, workspace=ws)
127
173
  n_sources = len(manifest.sources)
128
174
  n_pruned = sum(s.get("pruned", 0) for s in manifest.sources)
129
175
  n_flagged = sum(s.get("worklist_flagged", 0) for s in manifest.sources)
@@ -137,6 +183,13 @@ async def review_command(
137
183
  args: argparse.Namespace, # Parsed args for the `review` subcommand
138
184
  ) -> int: # Process exit code
139
185
  """Execute the `review` subcommand: interactive text corrections over the flagged worklist."""
186
+ # 5daadfc4 workspace: same early resolution + export as run_command.
187
+ ws = resolve_workspace(explicit=getattr(args, "workspace", None))
188
+ if ws is not None:
189
+ os.environ["CJM_WORKSPACE"] = str(ws.root)
190
+ if args.manifests_dir is None:
191
+ args.manifests_dir = (str(ws.substrate_data_dir / "manifests")
192
+ if ws is not None else ".cjm/manifests")
140
193
  manifest_path = str(Path(args.manifest).resolve())
141
194
  if not Path(manifest_path).exists():
142
195
  raise SystemExit(f"decomp manifest not found: {manifest_path}")
@@ -147,7 +200,7 @@ async def review_command(
147
200
 
148
201
  cfg = CorrectionConfig(graph_capability=args.graph_capability, graph_db_path=graph_db_path,
149
202
  actor=args.actor, assume_yes=args.yes, prune_empty=False,
150
- rendition_selector=args.rendition)
203
+ rendition_selector=args.rendition, skeleton_selector=args.skeleton)
151
204
  manager = CapabilityManager(search_paths=[Path(args.manifests_dir)], sysmon_capability_name=args.sysmon_capability)
152
205
  load_order = ([args.sysmon_capability] if args.sysmon_capability else []) + [cfg.graph_capability]
153
206
  load_capabilities(manager, load_order, configs={cfg.graph_capability: {"db_path": graph_db_path}})
@@ -167,8 +220,9 @@ async def review_command(
167
220
  except Exception as e: # Best-effort teardown; never mask the run's outcome
168
221
  logger.warning(f"unload {iid} failed: {e}")
169
222
 
170
- out = Path(args.output) if args.output else Path("runs") / f"{manifest.run_id}.json"
171
- manifest.save(out)
223
+ out = (Path(args.output) if args.output
224
+ else (ws.runs_dir if ws is not None else Path("runs")) / f"{manifest.run_id}.json")
225
+ manifest.save(out, workspace=ws)
172
226
  n_corr = sum(s.get("corrected", 0) for s in manifest.sources)
173
227
  n_active = sum(s.get("active_corrections", 0) for s in manifest.sources)
174
228
  print(f"correction manifest: {out}")
@@ -190,4 +244,99 @@ def main(
190
244
  return asyncio.run(run_command(args))
191
245
  if args.command == "review":
192
246
  return asyncio.run(review_command(args))
247
+ if args.command == "stats":
248
+ return asyncio.run(stats_command(args))
193
249
  raise SystemExit(f"unknown command: {args.command}")
250
+
251
+
252
+ async def stats_command(
253
+ args: argparse.Namespace, # Parsed args for the `stats` subcommand
254
+ ) -> int: # Process exit code
255
+ """Execute the `stats` subcommand: flywheel accounting over the shared graph.
256
+
257
+ The manual-tally retirement (drive ask 2026-07-27): per Source, the ACTIVE
258
+ overlay folds into counts — labeled insert spans, split boundaries, open
259
+ marks by class, op totals — plus the session purpose mix so dev-noise is
260
+ visible; --genuine-only cuts the counts to genuine-session corrections
261
+ (the DEC c86714a4 tag), --label spotlights one class corpus-wide (the
262
+ "how many active inhale spans?" one-liner)."""
263
+ ws = resolve_workspace(explicit=getattr(args, "workspace", None))
264
+ if ws is not None:
265
+ os.environ["CJM_WORKSPACE"] = str(ws.root)
266
+ if args.manifests_dir is None:
267
+ args.manifests_dir = (str(ws.substrate_data_dir / "manifests")
268
+ if ws is not None else ".cjm/manifests")
269
+ manager = CapabilityManager(search_paths=[Path(args.manifests_dir)])
270
+ configs = ({args.graph_capability: {"db_path": args.graph_db_path}}
271
+ if args.graph_db_path else None)
272
+ load_capabilities(manager, [args.graph_capability], configs=configs)
273
+ queue = JobQueue(deps=manager)
274
+ await queue.start()
275
+ try:
276
+ res = await graph_task(queue, args.graph_capability, "query_nodes",
277
+ query=NodeQuery(label="Source", project=["title"]).to_dict())
278
+ sources = [(r["id"], str(r.get("title") or "")) for r in (res.rows or [])]
279
+ if args.source:
280
+ needle = args.source.lower()
281
+ sources = [(i, t) for i, t in sources
282
+ if i == args.source or needle in t.lower()]
283
+ if not sources:
284
+ print("no matching Source nodes")
285
+ return 1
286
+ sres = await graph_task(queue, args.graph_capability, "query_nodes",
287
+ query=NodeQuery(label="CorrectionSession").to_dict())
288
+ genuine_ids = set()
289
+ purpose_of: Dict[str, str] = {}
290
+ for n in (sres.nodes or []):
291
+ d = n.to_dict() if hasattr(n, "to_dict") else n
292
+ purpose = str((d.get("properties") or {}).get("purpose") or "genuine")
293
+ purpose_of[d["id"]] = purpose
294
+ if purpose == "genuine":
295
+ genuine_ids.add(d["id"])
296
+ totals: Dict[str, Dict[str, int]] = {"insert_labels": {}, "mark_classes": {}}
297
+ total_splits = 0
298
+ for sid, title in sources:
299
+ corrections, superseded = await load_source_corrections(
300
+ queue, args.graph_capability, sid)
301
+ counted = corrections
302
+ if args.genuine_only:
303
+ # Purpose scopes the COUNTS; the superseded set stays global —
304
+ # supersession is spine state whoever committed it.
305
+ counted = [c for c in corrections if c.get("session_id") in genuine_ids]
306
+ st = correction_stats(counted, superseded)
307
+ mix: Dict[str, int] = {}
308
+ for c in corrections:
309
+ p = purpose_of.get(str(c.get("session_id")), "genuine")
310
+ mix[p] = mix.get(p, 0) + 1
311
+ print(f"== {title or sid} ({sid}) ==")
312
+ print(" ops by session purpose: "
313
+ + (" ".join(f"{k}x{v}" for k, v in sorted(mix.items())) or "none"))
314
+ print(f" active corrections: {st['active']} ("
315
+ + (" ".join(f"{k}={v}" for k, v in sorted(st["ops"].items())) or "none") + ")")
316
+ print(f" splits: {st['splits']}")
317
+ print(" labeled inserts: "
318
+ + (" · ".join(f"{k}x{v}" for k, v in sorted(st["insert_labels"].items())) or "none"))
319
+ print(f" open marks ({st['open_marks']}): "
320
+ + (" · ".join(f"{k}x{v}" for k, v in sorted(st["mark_classes"].items())) or "none"))
321
+ total_splits += st["splits"]
322
+ for k, v in st["insert_labels"].items():
323
+ totals["insert_labels"][k] = totals["insert_labels"].get(k, 0) + v
324
+ for k, v in st["mark_classes"].items():
325
+ totals["mark_classes"][k] = totals["mark_classes"].get(k, 0) + v
326
+ print(f"== TOTALS ({len(sources)} source(s)) ==")
327
+ print(f" splits: {total_splits}")
328
+ print(" labeled inserts: "
329
+ + (" · ".join(f"{k}x{v}" for k, v in sorted(totals["insert_labels"].items())) or "none"))
330
+ print(" open marks: "
331
+ + (" · ".join(f"{k}x{v}" for k, v in sorted(totals["mark_classes"].items())) or "none"))
332
+ if args.label:
333
+ ins = totals["insert_labels"].get(args.label, 0)
334
+ mk = totals["mark_classes"].get(args.label, 0)
335
+ print(f" active {args.label!r} spans: {ins + mk} (inserts {ins} + marks {mk})")
336
+ finally:
337
+ await queue.stop()
338
+ try:
339
+ manager.unload_capability(args.graph_capability)
340
+ except Exception as e: # Best-effort teardown; never mask the stats outcome
341
+ logger.warning(f"unload {args.graph_capability} failed: {e}")
342
+ return 0