cjm-transcript-correction-core 0.0.4__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 (27) hide show
  1. {cjm_transcript_correction_core-0.0.4/cjm_transcript_correction_core.egg-info → cjm_transcript_correction_core-0.0.5}/PKG-INFO +30 -4
  2. {cjm_transcript_correction_core-0.0.4 → cjm_transcript_correction_core-0.0.5}/README.md +29 -3
  3. cjm_transcript_correction_core-0.0.5/cjm_transcript_correction_core/__init__.py +1 -0
  4. {cjm_transcript_correction_core-0.0.4 → cjm_transcript_correction_core-0.0.5}/cjm_transcript_correction_core/cli.py +124 -2
  5. {cjm_transcript_correction_core-0.0.4 → cjm_transcript_correction_core-0.0.5}/cjm_transcript_correction_core/graph.py +903 -12
  6. {cjm_transcript_correction_core-0.0.4 → cjm_transcript_correction_core-0.0.5}/cjm_transcript_correction_core/journal.py +20 -1
  7. {cjm_transcript_correction_core-0.0.4 → cjm_transcript_correction_core-0.0.5}/cjm_transcript_correction_core/models.py +48 -1
  8. {cjm_transcript_correction_core-0.0.4 → cjm_transcript_correction_core-0.0.5}/cjm_transcript_correction_core/pipeline.py +10 -5
  9. {cjm_transcript_correction_core-0.0.4 → cjm_transcript_correction_core-0.0.5}/cjm_transcript_correction_core/signals.py +52 -1
  10. {cjm_transcript_correction_core-0.0.4 → cjm_transcript_correction_core-0.0.5/cjm_transcript_correction_core.egg-info}/PKG-INFO +30 -4
  11. cjm_transcript_correction_core-0.0.5/tests/test_graph.py +701 -0
  12. {cjm_transcript_correction_core-0.0.4 → cjm_transcript_correction_core-0.0.5}/tests/test_journal.py +8 -2
  13. {cjm_transcript_correction_core-0.0.4 → cjm_transcript_correction_core-0.0.5}/tests/test_models.py +33 -1
  14. {cjm_transcript_correction_core-0.0.4 → cjm_transcript_correction_core-0.0.5}/tests/test_signals.py +31 -0
  15. cjm_transcript_correction_core-0.0.4/cjm_transcript_correction_core/__init__.py +0 -1
  16. cjm_transcript_correction_core-0.0.4/tests/test_graph.py +0 -274
  17. {cjm_transcript_correction_core-0.0.4 → cjm_transcript_correction_core-0.0.5}/LICENSE +0 -0
  18. {cjm_transcript_correction_core-0.0.4 → cjm_transcript_correction_core-0.0.5}/MANIFEST.in +0 -0
  19. {cjm_transcript_correction_core-0.0.4 → cjm_transcript_correction_core-0.0.5}/cjm_transcript_correction_core.egg-info/SOURCES.txt +0 -0
  20. {cjm_transcript_correction_core-0.0.4 → cjm_transcript_correction_core-0.0.5}/cjm_transcript_correction_core.egg-info/dependency_links.txt +0 -0
  21. {cjm_transcript_correction_core-0.0.4 → cjm_transcript_correction_core-0.0.5}/cjm_transcript_correction_core.egg-info/entry_points.txt +0 -0
  22. {cjm_transcript_correction_core-0.0.4 → cjm_transcript_correction_core-0.0.5}/cjm_transcript_correction_core.egg-info/requires.txt +0 -0
  23. {cjm_transcript_correction_core-0.0.4 → cjm_transcript_correction_core-0.0.5}/cjm_transcript_correction_core.egg-info/top_level.txt +0 -0
  24. {cjm_transcript_correction_core-0.0.4 → cjm_transcript_correction_core-0.0.5}/pyproject.toml +0 -0
  25. {cjm_transcript_correction_core-0.0.4 → cjm_transcript_correction_core-0.0.5}/setup.cfg +0 -0
  26. {cjm_transcript_correction_core-0.0.4 → cjm_transcript_correction_core-0.0.5}/tests/test_cli.py +0 -0
  27. {cjm_transcript_correction_core-0.0.4 → 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.4
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
@@ -40,43 +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).
49
56
  - `build_mark_correction` _function_ — Build a NON-MUTATING mark Correction (DEC 2a231843: routed attention).
50
57
  - `build_prune_amendment` _function_ — Build a grouping Correction that supersedes a prune with a REDUCED set (unprune).
51
58
  - `build_prune_correction` _function_ — Build one batch grouping Correction that prunes empty segments (D14).
52
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).
53
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).
54
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
55
68
  - `commit_mark_correction` _function_ — Commit a mark (node + CORRECTS per anchored segment [+ SUPERSEDES]).
56
69
  - `commit_mark_dismissal` _function_ — Dismiss an open mark WITHOUT a correction (reject-as-supersede).
57
70
  - `commit_nodes_edges` _function_ — Commit overlay nodes/edges through the layer's idempotent extend_graph.
58
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).
59
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).
60
77
  - `corrections_to_edits` _function_ — Map this core's Correction payloads onto the layer's spine-edit vocabulary.
61
- - `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).
62
79
  - `find_active_text_correction` _function_ — Single-segment convenience over the batch read (cross-session; latest wins).
63
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.
64
82
  - `find_corrections_for_session` _function_ — List corrections recorded in a session (typed property filter).
65
83
  - `find_prior_corrections_by_hash` _function_ — Cross-transcript correction-cache lookup (targeted; the graph IS the lexicon).
66
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).
67
87
  - `load_empty_segments` _function_ — Load ONLY a Source's empty-text segments under its chosen rendition (D14 prune).
68
88
  - `load_review_markers` _function_ — Load a session's review markers (typed edge projection over REVIEWED edges).
69
89
  - `load_source_corrections` _function_ — Load every Correction targeting a Source (across sessions) + the superseded-id set.
70
- - `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).
71
91
  - `load_variant_texts` _function_ — Resolve per-transcriber chunk texts from the segments' CharSlice refs.
72
92
  - `mark_anchor_segments` _function_ — Validate a mark anchor and list the Segment ids it touches.
73
93
  - `open_marks` _function_ — Filter to the OPEN marks — the pass-2 worklist ('query open marks, walk them').
74
94
  - `project_effective_spine` _function_ — Project the effective spine = layer-0 + applied corrections.
75
95
  - `reanchor_span` _function_ — Re-locate a span anchor in text that may have been edited since mark time.
76
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).
77
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).
78
101
  - `set_session_status` _function_ — Update a session's status + updated_at.
79
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).
80
104
  - `start_session` _function_ — Create + commit a new CorrectionSession node.
81
105
  - `submit_and_wait` _function_ — Submit one capability job, wait for it, return its result (raise on failure).
82
106
 
@@ -93,6 +117,7 @@ A frontend-agnostic core for the transcript correction workflow — the first do
93
117
  - `CorrectionManifest` _class_ — Durable record of one correction run (proto-bundle; chainable, CR-20).
94
118
  - `CorrectionRelations` _class_ — Registry of edge types the correction overlay adds to the spine graph.
95
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).
96
121
  - `SpineSegment` _class_ — A committed layer-0 Segment loaded from the graph (read view).
97
122
  - `WorklistItem` _class_ — One spine segment surfaced for review, with its deterministic Tier-1 flags.
98
123
  - `new_run_id` _function_ — Generate a unique, sortable correction run id.
@@ -118,9 +143,10 @@ A frontend-agnostic core for the transcript correction workflow — the first do
118
143
  - `fa_coverage_flags` _function_ — Flag segments whose forced-alignment coverage looks suspect (Tier-1).
119
144
  - `levenshtein` _function_ — Levenshtein edit distance (pure, in-core; variant-clustering primitive).
120
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.
121
147
  - `variant_divergence` _function_ — Within-segment cross-transcriber divergence (stage 5: intra-graph).
122
148
 
123
149
  ## Dependencies
124
150
 
125
151
  **Depends on:** `cjm-context-graph-layer`, `cjm-context-graph-primitives`, `cjm-substrate`, `cjm-transcript-graph-schema`
126
- **Used by:** `cjm-transcript-correction-tui`
152
+ **Used by:** `cjm-transcript-correction-tui`, `cjm-workflow-hub-tui`
@@ -22,43 +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).
31
38
  - `build_mark_correction` _function_ — Build a NON-MUTATING mark Correction (DEC 2a231843: routed attention).
32
39
  - `build_prune_amendment` _function_ — Build a grouping Correction that supersedes a prune with a REDUCED set (unprune).
33
40
  - `build_prune_correction` _function_ — Build one batch grouping Correction that prunes empty segments (D14).
34
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).
35
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).
36
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
37
50
  - `commit_mark_correction` _function_ — Commit a mark (node + CORRECTS per anchored segment [+ SUPERSEDES]).
38
51
  - `commit_mark_dismissal` _function_ — Dismiss an open mark WITHOUT a correction (reject-as-supersede).
39
52
  - `commit_nodes_edges` _function_ — Commit overlay nodes/edges through the layer's idempotent extend_graph.
40
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).
41
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).
42
59
  - `corrections_to_edits` _function_ — Map this core's Correction payloads onto the layer's spine-edit vocabulary.
43
- - `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).
44
61
  - `find_active_text_correction` _function_ — Single-segment convenience over the batch read (cross-session; latest wins).
45
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.
46
64
  - `find_corrections_for_session` _function_ — List corrections recorded in a session (typed property filter).
47
65
  - `find_prior_corrections_by_hash` _function_ — Cross-transcript correction-cache lookup (targeted; the graph IS the lexicon).
48
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).
49
69
  - `load_empty_segments` _function_ — Load ONLY a Source's empty-text segments under its chosen rendition (D14 prune).
50
70
  - `load_review_markers` _function_ — Load a session's review markers (typed edge projection over REVIEWED edges).
51
71
  - `load_source_corrections` _function_ — Load every Correction targeting a Source (across sessions) + the superseded-id set.
52
- - `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).
53
73
  - `load_variant_texts` _function_ — Resolve per-transcriber chunk texts from the segments' CharSlice refs.
54
74
  - `mark_anchor_segments` _function_ — Validate a mark anchor and list the Segment ids it touches.
55
75
  - `open_marks` _function_ — Filter to the OPEN marks — the pass-2 worklist ('query open marks, walk them').
56
76
  - `project_effective_spine` _function_ — Project the effective spine = layer-0 + applied corrections.
57
77
  - `reanchor_span` _function_ — Re-locate a span anchor in text that may have been edited since mark time.
58
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).
59
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).
60
83
  - `set_session_status` _function_ — Update a session's status + updated_at.
61
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).
62
86
  - `start_session` _function_ — Create + commit a new CorrectionSession node.
63
87
  - `submit_and_wait` _function_ — Submit one capability job, wait for it, return its result (raise on failure).
64
88
 
@@ -75,6 +99,7 @@ A frontend-agnostic core for the transcript correction workflow — the first do
75
99
  - `CorrectionManifest` _class_ — Durable record of one correction run (proto-bundle; chainable, CR-20).
76
100
  - `CorrectionRelations` _class_ — Registry of edge types the correction overlay adds to the spine graph.
77
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).
78
103
  - `SpineSegment` _class_ — A committed layer-0 Segment loaded from the graph (read view).
79
104
  - `WorklistItem` _class_ — One spine segment surfaced for review, with its deterministic Tier-1 flags.
80
105
  - `new_run_id` _function_ — Generate a unique, sortable correction run id.
@@ -100,9 +125,10 @@ A frontend-agnostic core for the transcript correction workflow — the first do
100
125
  - `fa_coverage_flags` _function_ — Flag segments whose forced-alignment coverage looks suspect (Tier-1).
101
126
  - `levenshtein` _function_ — Levenshtein edit distance (pure, in-core; variant-clustering primitive).
102
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.
103
129
  - `variant_divergence` _function_ — Within-segment cross-transcriber divergence (stage 5: intra-graph).
104
130
 
105
131
  ## Dependencies
106
132
 
107
133
  **Depends on:** `cjm-context-graph-layer`, `cjm-context-graph-primitives`, `cjm-substrate`, `cjm-transcript-graph-schema`
108
- **Used by:** `cjm-transcript-correction-tui`
134
+ **Used by:** `cjm-transcript-correction-tui`, `cjm-workflow-hub-tui`
@@ -7,9 +7,12 @@ import os
7
7
  from pathlib import Path
8
8
  from typing import Dict, List, Optional
9
9
 
10
+ from cjm_context_graph_layer.ops import graph_task
11
+ from cjm_context_graph_primitives.query import NodeQuery
10
12
  from cjm_substrate.core.manager import CapabilityManager
11
13
  from cjm_substrate.core.queue import JobQueue
12
14
  from cjm_substrate.core.workspace import resolve_workspace
15
+ from cjm_transcript_correction_core.graph import correction_stats, load_source_corrections
13
16
  from cjm_transcript_correction_core.models import CorrectionConfig
14
17
  from cjm_transcript_correction_core.pipeline import (load_decomp_manifest, resolve_graph_db_path,
15
18
  run_correction, run_review)
@@ -34,6 +37,10 @@ def _add_common_run_args(p: argparse.ArgumentParser) -> None: # Shared run/revi
34
37
  help="Which AudioRendition spine to correct when a source has more than one "
35
38
  "(\"raw\" or a preprocessing substring e.g. \"demucs\"); default: auto-select the "
36
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)")
37
44
  p.add_argument("--sysmon-capability", default=None,
38
45
  help="monitor for empirical attribution (CR-7); loaded first; default: none")
39
46
  p.add_argument("--session", default=None, help="Resume an existing CorrectionSession id")
@@ -66,6 +73,26 @@ def build_parser() -> argparse.ArgumentParser: # Configured CLI parser
66
73
  _add_common_run_args(review)
67
74
  review.add_argument("--review-max", type=int, default=0, help="Max worklist items to review (0 = all)")
68
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")
69
96
  return parser
70
97
 
71
98
 
@@ -114,7 +141,7 @@ async def run_command(
114
141
  cfg = CorrectionConfig(
115
142
  graph_capability=args.graph_capability, graph_db_path=graph_db_path,
116
143
  actor=args.actor, assume_yes=args.yes, prune_empty=not args.no_prune,
117
- rendition_selector=args.rendition,
144
+ rendition_selector=args.rendition, skeleton_selector=args.skeleton,
118
145
  )
119
146
 
120
147
  manager = CapabilityManager(
@@ -173,7 +200,7 @@ async def review_command(
173
200
 
174
201
  cfg = CorrectionConfig(graph_capability=args.graph_capability, graph_db_path=graph_db_path,
175
202
  actor=args.actor, assume_yes=args.yes, prune_empty=False,
176
- rendition_selector=args.rendition)
203
+ rendition_selector=args.rendition, skeleton_selector=args.skeleton)
177
204
  manager = CapabilityManager(search_paths=[Path(args.manifests_dir)], sysmon_capability_name=args.sysmon_capability)
178
205
  load_order = ([args.sysmon_capability] if args.sysmon_capability else []) + [cfg.graph_capability]
179
206
  load_capabilities(manager, load_order, configs={cfg.graph_capability: {"db_path": graph_db_path}})
@@ -217,4 +244,99 @@ def main(
217
244
  return asyncio.run(run_command(args))
218
245
  if args.command == "review":
219
246
  return asyncio.run(review_command(args))
247
+ if args.command == "stats":
248
+ return asyncio.run(stats_command(args))
220
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