cjm-transcript-correction-core 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.
- cjm_transcript_correction_core/__init__.py +1 -0
- cjm_transcript_correction_core/cli.py +193 -0
- cjm_transcript_correction_core/graph.py +783 -0
- cjm_transcript_correction_core/models.py +171 -0
- cjm_transcript_correction_core/pipeline.py +489 -0
- cjm_transcript_correction_core/signals.py +208 -0
- cjm_transcript_correction_core-0.0.1.dist-info/METADATA +135 -0
- cjm_transcript_correction_core-0.0.1.dist-info/RECORD +12 -0
- cjm_transcript_correction_core-0.0.1.dist-info/WHEEL +5 -0
- cjm_transcript_correction_core-0.0.1.dist-info/entry_points.txt +2 -0
- cjm_transcript_correction_core-0.0.1.dist-info/licenses/LICENSE +201 -0
- cjm_transcript_correction_core-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.1"
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""The CLI driver — the correction core's first (and currently only) frontend. run <decomp-manifest> corrects the committed spine in the decomp graph DB, pointing the graph worker at that shared DB via load-time config, with optional session resume/reopen; review runs the interactive text-correction loop (the cross-transcriber diff is intra-graph since stage 5)."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import asyncio
|
|
5
|
+
import logging
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Dict, List, Optional
|
|
8
|
+
|
|
9
|
+
from cjm_substrate.core.manager import CapabilityManager
|
|
10
|
+
from cjm_substrate.core.queue import JobQueue
|
|
11
|
+
from cjm_transcript_correction_core.models import CorrectionConfig
|
|
12
|
+
from cjm_transcript_correction_core.pipeline import (load_decomp_manifest, resolve_graph_db_path,
|
|
13
|
+
run_correction, run_review)
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _add_common_run_args(p: argparse.ArgumentParser) -> None: # Shared run/review arguments
|
|
19
|
+
"""Attach the capability / session / output arguments shared by `run` and `review`."""
|
|
20
|
+
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")
|
|
22
|
+
p.add_argument("--graph-capability", default="cjm-capability-graph-sqlite", help="Graph-storage capability name")
|
|
23
|
+
p.add_argument("--graph-db-path", default=None,
|
|
24
|
+
help="Override graph DB path (default: the decomp manifest's recorded db_path)")
|
|
25
|
+
p.add_argument("--rendition", default=None,
|
|
26
|
+
help="Which AudioRendition spine to correct when a source has more than one "
|
|
27
|
+
"(\"raw\" or a preprocessing substring e.g. \"demucs\"); default: auto-select the "
|
|
28
|
+
"decomposed one (errors if ambiguous)")
|
|
29
|
+
p.add_argument("--sysmon-capability", default=None,
|
|
30
|
+
help="monitor for empirical attribution (CR-7); loaded first; default: none")
|
|
31
|
+
p.add_argument("--session", default=None, help="Resume an existing CorrectionSession id")
|
|
32
|
+
p.add_argument("--reopen", action="store_true", help="Reopen a completed session (with --session)")
|
|
33
|
+
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)")
|
|
35
|
+
p.add_argument("-v", "--verbose", action="store_true", help="DEBUG-level logging")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def build_parser() -> argparse.ArgumentParser: # Configured CLI parser
|
|
39
|
+
"""Build the CLI parser (subcommands: run, review).
|
|
40
|
+
|
|
41
|
+
Stage 5: --secondary-manifest is RETIRED — the cross-transcriber diff is
|
|
42
|
+
intra-graph now (variant slices on the shared-skeleton segments).
|
|
43
|
+
"""
|
|
44
|
+
parser = argparse.ArgumentParser(
|
|
45
|
+
prog="cjm-transcript-correction-core",
|
|
46
|
+
description="Headless transcript correction: non-destructive overlay on a committed source spine.",
|
|
47
|
+
)
|
|
48
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
49
|
+
|
|
50
|
+
run = sub.add_parser("run", help="Prune empty segments + surface the worklist (deterministic)")
|
|
51
|
+
_add_common_run_args(run)
|
|
52
|
+
run.add_argument("--no-prune", action="store_true", help="Skip the D14 empty-segment prune")
|
|
53
|
+
run.add_argument("-y", "--yes", action="store_true", help="Auto-accept HITL seams (headless mode)")
|
|
54
|
+
|
|
55
|
+
review = sub.add_parser("review", help="Interactive text-correction review of the flagged worklist")
|
|
56
|
+
_add_common_run_args(review)
|
|
57
|
+
review.add_argument("--review-max", type=int, default=0, help="Max worklist items to review (0 = all)")
|
|
58
|
+
review.add_argument("-y", "--yes", action="store_true", help="Auto-mark every reviewed item (no edits)")
|
|
59
|
+
return parser
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def load_capabilities(
|
|
63
|
+
manager: CapabilityManager, # Freshly constructed manager
|
|
64
|
+
instance_ids: List[str], # Capability names to load, in order
|
|
65
|
+
configs: Optional[Dict[str, Dict]] = None, # Per-capability load-time config (e.g. graph db_path)
|
|
66
|
+
) -> None:
|
|
67
|
+
"""Discover manifests + load each capability, passing per-capability config (CR-2 caller-wins)."""
|
|
68
|
+
configs = configs or {}
|
|
69
|
+
manager.discover_manifests()
|
|
70
|
+
discovered = {m.name: m for m in manager.discovered}
|
|
71
|
+
for iid in instance_ids:
|
|
72
|
+
meta = discovered.get(iid)
|
|
73
|
+
if meta is None:
|
|
74
|
+
raise SystemExit(
|
|
75
|
+
f"capability {iid!r} not found in manifests "
|
|
76
|
+
f"(discovered: {sorted(discovered)}) -- run cjm-ctl install-all first"
|
|
77
|
+
)
|
|
78
|
+
if not manager.load_capability(meta, config=configs.get(iid)):
|
|
79
|
+
raise SystemExit(f"failed to load capability {iid!r}")
|
|
80
|
+
logger.info(f"loaded {iid}" + (f" (db_path override)" if iid in configs else ""))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
async def run_command(
|
|
84
|
+
args: argparse.Namespace, # Parsed args for the `run` subcommand
|
|
85
|
+
) -> int: # Process exit code
|
|
86
|
+
"""Execute the `run` subcommand: correct a decomp manifest's committed spine."""
|
|
87
|
+
manifest_path = str(Path(args.manifest).resolve())
|
|
88
|
+
if not Path(manifest_path).exists():
|
|
89
|
+
raise SystemExit(f"decomp manifest not found: {manifest_path}")
|
|
90
|
+
|
|
91
|
+
decomp = load_decomp_manifest(manifest_path)
|
|
92
|
+
graph_db_path = resolve_graph_db_path(decomp, args.graph_capability, override=args.graph_db_path)
|
|
93
|
+
if not graph_db_path:
|
|
94
|
+
raise SystemExit("could not resolve graph DB path from manifest; pass --graph-db-path explicitly")
|
|
95
|
+
|
|
96
|
+
cfg = CorrectionConfig(
|
|
97
|
+
graph_capability=args.graph_capability, graph_db_path=graph_db_path,
|
|
98
|
+
actor=args.actor, assume_yes=args.yes, prune_empty=not args.no_prune,
|
|
99
|
+
rendition_selector=args.rendition,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
manager = CapabilityManager(
|
|
103
|
+
search_paths=[Path(args.manifests_dir)],
|
|
104
|
+
sysmon_capability_name=args.sysmon_capability,
|
|
105
|
+
)
|
|
106
|
+
load_order = ([args.sysmon_capability] if args.sysmon_capability else []) + [cfg.graph_capability]
|
|
107
|
+
# Point the graph worker at the decomp graph DB (the shared spine) via load-time config.
|
|
108
|
+
load_capabilities(manager, load_order, configs={cfg.graph_capability: {"db_path": graph_db_path}})
|
|
109
|
+
|
|
110
|
+
queue = JobQueue(deps=manager, sysmon_capability_name=args.sysmon_capability)
|
|
111
|
+
await queue.start()
|
|
112
|
+
try:
|
|
113
|
+
manifest = await run_correction(
|
|
114
|
+
manager, queue, cfg, manifest_path, graph_db_path,
|
|
115
|
+
session_id=args.session, reopen=args.reopen,
|
|
116
|
+
)
|
|
117
|
+
finally:
|
|
118
|
+
await queue.stop()
|
|
119
|
+
for iid in reversed(load_order):
|
|
120
|
+
try:
|
|
121
|
+
manager.unload_capability(iid)
|
|
122
|
+
except Exception as e: # Best-effort teardown; never mask the run's outcome
|
|
123
|
+
logger.warning(f"unload {iid} failed: {e}")
|
|
124
|
+
|
|
125
|
+
out = Path(args.output) if args.output else Path("runs") / f"{manifest.run_id}.json"
|
|
126
|
+
manifest.save(out)
|
|
127
|
+
n_sources = len(manifest.sources)
|
|
128
|
+
n_pruned = sum(s.get("pruned", 0) for s in manifest.sources)
|
|
129
|
+
n_flagged = sum(s.get("worklist_flagged", 0) for s in manifest.sources)
|
|
130
|
+
print(f"correction manifest: {out}")
|
|
131
|
+
print(f"sources: {n_sources} worklist flagged: {n_flagged} pruned: {n_pruned}")
|
|
132
|
+
print(f"session: {manifest.session_id}")
|
|
133
|
+
return 0
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
async def review_command(
|
|
137
|
+
args: argparse.Namespace, # Parsed args for the `review` subcommand
|
|
138
|
+
) -> int: # Process exit code
|
|
139
|
+
"""Execute the `review` subcommand: interactive text corrections over the flagged worklist."""
|
|
140
|
+
manifest_path = str(Path(args.manifest).resolve())
|
|
141
|
+
if not Path(manifest_path).exists():
|
|
142
|
+
raise SystemExit(f"decomp manifest not found: {manifest_path}")
|
|
143
|
+
decomp = load_decomp_manifest(manifest_path)
|
|
144
|
+
graph_db_path = resolve_graph_db_path(decomp, args.graph_capability, override=args.graph_db_path)
|
|
145
|
+
if not graph_db_path:
|
|
146
|
+
raise SystemExit("could not resolve graph DB path from manifest; pass --graph-db-path explicitly")
|
|
147
|
+
|
|
148
|
+
cfg = CorrectionConfig(graph_capability=args.graph_capability, graph_db_path=graph_db_path,
|
|
149
|
+
actor=args.actor, assume_yes=args.yes, prune_empty=False,
|
|
150
|
+
rendition_selector=args.rendition)
|
|
151
|
+
manager = CapabilityManager(search_paths=[Path(args.manifests_dir)], sysmon_capability_name=args.sysmon_capability)
|
|
152
|
+
load_order = ([args.sysmon_capability] if args.sysmon_capability else []) + [cfg.graph_capability]
|
|
153
|
+
load_capabilities(manager, load_order, configs={cfg.graph_capability: {"db_path": graph_db_path}})
|
|
154
|
+
|
|
155
|
+
queue = JobQueue(deps=manager, sysmon_capability_name=args.sysmon_capability)
|
|
156
|
+
await queue.start()
|
|
157
|
+
try:
|
|
158
|
+
manifest = await run_review(
|
|
159
|
+
manager, queue, cfg, manifest_path, graph_db_path,
|
|
160
|
+
session_id=args.session, reopen=args.reopen, max_items=args.review_max,
|
|
161
|
+
)
|
|
162
|
+
finally:
|
|
163
|
+
await queue.stop()
|
|
164
|
+
for iid in reversed(load_order):
|
|
165
|
+
try:
|
|
166
|
+
manager.unload_capability(iid)
|
|
167
|
+
except Exception as e: # Best-effort teardown; never mask the run's outcome
|
|
168
|
+
logger.warning(f"unload {iid} failed: {e}")
|
|
169
|
+
|
|
170
|
+
out = Path(args.output) if args.output else Path("runs") / f"{manifest.run_id}.json"
|
|
171
|
+
manifest.save(out)
|
|
172
|
+
n_corr = sum(s.get("corrected", 0) for s in manifest.sources)
|
|
173
|
+
n_active = sum(s.get("active_corrections", 0) for s in manifest.sources)
|
|
174
|
+
print(f"correction manifest: {out}")
|
|
175
|
+
print(f"sources: {len(manifest.sources)} corrected: {n_corr} active corrections: {n_active}")
|
|
176
|
+
print(f"session: {manifest.session_id}")
|
|
177
|
+
return 0
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def main(
|
|
181
|
+
argv: Optional[List[str]] = None, # Argument list override (None = sys.argv)
|
|
182
|
+
) -> int: # Process exit code
|
|
183
|
+
"""CLI entry point (console script: `cjm-transcript-correction-core`)."""
|
|
184
|
+
args = build_parser().parse_args(argv)
|
|
185
|
+
logging.basicConfig(
|
|
186
|
+
level=logging.DEBUG if args.verbose else logging.INFO,
|
|
187
|
+
format="%(asctime)s [%(levelname)s] %(name)s :: %(message)s",
|
|
188
|
+
)
|
|
189
|
+
if args.command == "run":
|
|
190
|
+
return asyncio.run(run_command(args))
|
|
191
|
+
if args.command == "review":
|
|
192
|
+
return asyncio.run(review_command(args))
|
|
193
|
+
raise SystemExit(f"unknown command: {args.command}")
|