cjm-transcript-graph-schema 0.0.5__tar.gz → 0.0.7__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.
- {cjm_transcript_graph_schema-0.0.5/cjm_transcript_graph_schema.egg-info → cjm_transcript_graph_schema-0.0.7}/PKG-INFO +114 -46
- {cjm_transcript_graph_schema-0.0.5 → cjm_transcript_graph_schema-0.0.7}/README.md +111 -43
- cjm_transcript_graph_schema-0.0.7/cjm_transcript_graph_schema/__init__.py +1 -0
- {cjm_transcript_graph_schema-0.0.5 → cjm_transcript_graph_schema-0.0.7}/cjm_transcript_graph_schema/_modidx.py +11 -1
- {cjm_transcript_graph_schema-0.0.5 → cjm_transcript_graph_schema-0.0.7}/cjm_transcript_graph_schema/schema.py +123 -53
- {cjm_transcript_graph_schema-0.0.5 → cjm_transcript_graph_schema-0.0.7/cjm_transcript_graph_schema.egg-info}/PKG-INFO +114 -46
- cjm_transcript_graph_schema-0.0.7/cjm_transcript_graph_schema.egg-info/requires.txt +2 -0
- {cjm_transcript_graph_schema-0.0.5 → cjm_transcript_graph_schema-0.0.7}/pyproject.toml +1 -1
- cjm_transcript_graph_schema-0.0.5/cjm_transcript_graph_schema/__init__.py +0 -1
- cjm_transcript_graph_schema-0.0.5/cjm_transcript_graph_schema.egg-info/requires.txt +0 -2
- {cjm_transcript_graph_schema-0.0.5 → cjm_transcript_graph_schema-0.0.7}/LICENSE +0 -0
- {cjm_transcript_graph_schema-0.0.5 → cjm_transcript_graph_schema-0.0.7}/MANIFEST.in +0 -0
- {cjm_transcript_graph_schema-0.0.5 → cjm_transcript_graph_schema-0.0.7}/cjm_transcript_graph_schema.egg-info/SOURCES.txt +0 -0
- {cjm_transcript_graph_schema-0.0.5 → cjm_transcript_graph_schema-0.0.7}/cjm_transcript_graph_schema.egg-info/dependency_links.txt +0 -0
- {cjm_transcript_graph_schema-0.0.5 → cjm_transcript_graph_schema-0.0.7}/cjm_transcript_graph_schema.egg-info/entry_points.txt +0 -0
- {cjm_transcript_graph_schema-0.0.5 → cjm_transcript_graph_schema-0.0.7}/cjm_transcript_graph_schema.egg-info/top_level.txt +0 -0
- {cjm_transcript_graph_schema-0.0.5 → cjm_transcript_graph_schema-0.0.7}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cjm-transcript-graph-schema
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.7
|
|
4
4
|
Summary: Audio-transcript layer schema for context graphs: Source, AudioSegment, Transcript, and Segment nodes with deterministic identity tuples and graph-node mapping, shared by the transcription, decomposition, and correction workflow cores.
|
|
5
5
|
Author-email: "Christian J. Mills" <9126128+cj-mills@users.noreply.github.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -12,8 +12,8 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
12
12
|
Requires-Python: >=3.12
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
License-File: LICENSE
|
|
15
|
-
Requires-Dist: cjm-context-graph-layer>=0.0.
|
|
16
|
-
Requires-Dist: cjm-context-graph-primitives>=0.0.
|
|
15
|
+
Requires-Dist: cjm-context-graph-layer>=0.0.7
|
|
16
|
+
Requires-Dist: cjm-context-graph-primitives>=0.0.12
|
|
17
17
|
Dynamic: license-file
|
|
18
18
|
|
|
19
19
|
# cjm-transcript-graph-schema
|
|
@@ -30,7 +30,7 @@ pip install cjm_transcript_graph_schema
|
|
|
30
30
|
## Project Structure
|
|
31
31
|
|
|
32
32
|
nbs/
|
|
33
|
-
└── schema.ipynb # The audio-transcript layer schema (where-graph-begins locked layer schema): `Source -> AudioSegment(coarse
|
|
33
|
+
└── schema.ipynb # The audio-transcript layer schema (where-graph-begins locked layer schema): `Source -> AudioSegment(coarse boundary) -> AudioRendition(model-input: raw | vocals | …) -> Transcript(per-transcriber variants)` emitted by transcription, extended with the fine `Segment` spine (per-rendition) by decomposition. Deterministic identity tuples per the stage-5 ratified rule; the `AudioRendition` node lets raw + preprocessed model-inputs of one boundary coexist in one graph. `Document` from the pre-CR-18 era dissolves into `Source`.
|
|
34
34
|
|
|
35
35
|
Total: 1 notebook
|
|
36
36
|
|
|
@@ -55,10 +55,12 @@ Detailed documentation for each module in the project:
|
|
|
55
55
|
|
|
56
56
|
> The audio-transcript layer schema (where-graph-begins locked layer
|
|
57
57
|
> schema):
|
|
58
|
-
> `Source -> AudioSegment(coarse
|
|
59
|
-
> emitted by transcription, extended with the fine `Segment` spine
|
|
60
|
-
> decomposition. Deterministic identity tuples per
|
|
61
|
-
> rule; `
|
|
58
|
+
> `Source -> AudioSegment(coarse boundary) -> AudioRendition(model-input: raw | vocals | …) -> Transcript(per-transcriber variants)`
|
|
59
|
+
> emitted by transcription, extended with the fine `Segment` spine
|
|
60
|
+
> (per-rendition) by decomposition. Deterministic identity tuples per
|
|
61
|
+
> the stage-5 ratified rule; the `AudioRendition` node lets raw +
|
|
62
|
+
> preprocessed model-inputs of one boundary coexist in one graph.
|
|
63
|
+
> `Document` from the pre-CR-18 era dissolves into `Source`.
|
|
62
64
|
|
|
63
65
|
#### Import
|
|
64
66
|
|
|
@@ -67,10 +69,12 @@ from cjm_transcript_graph_schema.schema import (
|
|
|
67
69
|
TranscriptGraphLabels,
|
|
68
70
|
source_node_id,
|
|
69
71
|
audio_segment_node_id,
|
|
72
|
+
audio_rendition_node_id,
|
|
70
73
|
transcript_node_id,
|
|
71
74
|
segment_node_id,
|
|
72
75
|
SourceNode,
|
|
73
76
|
AudioSegmentNode,
|
|
77
|
+
AudioRenditionNode,
|
|
74
78
|
TranscriptNode,
|
|
75
79
|
TranscriptSliceRef,
|
|
76
80
|
SegmentNode
|
|
@@ -95,36 +99,60 @@ def audio_segment_node_id(
|
|
|
95
99
|
"""
|
|
96
100
|
AudioSegment identity = (source, boundary range).
|
|
97
101
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
re-derivation reproduces
|
|
102
|
+
Rendition-independent by design: an AudioSegment is a BOUNDARY of the source
|
|
103
|
+
(an audio fact), shared across every model-input rendition of it. The
|
|
104
|
+
boundary computation is pure and deterministic, so re-derivation reproduces
|
|
105
|
+
the id; the model-input WAV lives on the `AudioRendition` child, not here.
|
|
101
106
|
"""
|
|
102
107
|
```
|
|
103
108
|
|
|
104
109
|
``` python
|
|
105
|
-
def
|
|
110
|
+
def audio_rendition_node_id(
|
|
106
111
|
audio_segment_id: str, # Owning AudioSegment node id
|
|
107
|
-
|
|
108
|
-
|
|
112
|
+
chain: List[str], # Ordered preprocessing-chain descriptors; [] = the raw convert-only rendition
|
|
113
|
+
) -> str: # Deterministic AudioRendition node id
|
|
114
|
+
"""
|
|
115
|
+
AudioRendition identity = (audio segment, preprocessing chain).
|
|
116
|
+
|
|
117
|
+
A rendition is one model-input audio OF an AudioSegment. The chain is the
|
|
118
|
+
ordered list of preprocessing steps that produced it (each an opaque
|
|
119
|
+
descriptor string, e.g. ``"source_separation:cjm-media-plugin-demucs@<cfg>"``);
|
|
120
|
+
an EMPTY chain is the raw convert-only rendition, whose id is therefore
|
|
121
|
+
stable and chain-free. The universal 16k-mono resample is implicit (not a
|
|
122
|
+
chain step) — so raw + vocals are distinct renditions under one shared
|
|
123
|
+
AudioSegment, and the layer's identity-mismatch check never collides them.
|
|
124
|
+
Re-derivable from the manifest chain alone (extenders recompute, never
|
|
125
|
+
search).
|
|
126
|
+
"""
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
``` python
|
|
130
|
+
def transcript_node_id(
|
|
131
|
+
rendition_id: str, # Owning AudioRendition node id
|
|
132
|
+
transcriber: str, # Transcriber capability name (e.g. "cjm-transcription-plugin-whisper")
|
|
133
|
+
config_hash: str, # Transcriber config hash
|
|
109
134
|
) -> str: # Deterministic Transcript node id
|
|
110
135
|
"""
|
|
111
|
-
Transcript identity = (audio
|
|
136
|
+
Transcript identity = (audio rendition, transcriber, config) — MIRRORS the
|
|
112
137
|
capability cache key UNIQUE(audio_path, config_hash), so the graph node is
|
|
113
|
-
the durable face of the cached row (the structural E13 fix).
|
|
138
|
+
the durable face of the cached row (the structural E13 fix). Keyed on the
|
|
139
|
+
RENDITION, so a raw vs vocals transcript of the same boundary are distinct.
|
|
114
140
|
"""
|
|
115
141
|
```
|
|
116
142
|
|
|
117
143
|
``` python
|
|
118
144
|
def segment_node_id(
|
|
119
|
-
|
|
120
|
-
vad_config_hash: str,
|
|
121
|
-
chunk_start: float,
|
|
122
|
-
chunk_end: float,
|
|
145
|
+
rendition_id: str, # Owning AudioRendition node id
|
|
146
|
+
vad_config_hash: str, # VAD capability config hash (skeleton identity input)
|
|
147
|
+
chunk_start: float, # VAD chunk start (chunk-local seconds within the rendition WAV)
|
|
148
|
+
chunk_end: float, # VAD chunk end (chunk-local seconds)
|
|
123
149
|
) -> str: # Deterministic Segment node id
|
|
124
150
|
"""
|
|
125
|
-
Fine Segment identity = audio-side only (audio
|
|
151
|
+
Fine Segment identity = audio-side only (audio rendition, VAD config, chunk
|
|
126
152
|
range) — so the skeleton's ids are SHARED across transcribers by
|
|
127
|
-
construction (C4 "store agreement once" falls out of identity design).
|
|
153
|
+
construction (C4 "store agreement once" falls out of identity design). Keyed
|
|
154
|
+
on the RENDITION: each rendition has its own fine spine (vocals isolation can
|
|
155
|
+
yield different VAD chunk boundaries than raw).
|
|
128
156
|
"""
|
|
129
157
|
```
|
|
130
158
|
|
|
@@ -166,23 +194,23 @@ class SourceNode:
|
|
|
166
194
|
@dataclass
|
|
167
195
|
class AudioSegmentNode:
|
|
168
196
|
"""
|
|
169
|
-
Coarse ~5-min spine member
|
|
170
|
-
|
|
197
|
+
Coarse ~5-min spine member: a BOUNDARY range of the Source (an audio fact),
|
|
198
|
+
shared across every model-input rendition of it.
|
|
171
199
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
200
|
+
Hashless by design: a boundary is not a materialized artifact — a hash over
|
|
201
|
+
the sliced ORIGINAL source bytes is not materializable without extracting
|
|
202
|
+
per-range artifacts (decoded ranges are not byte ranges). The materialized
|
|
203
|
+
model-input WAV lives on the `AudioRendition` child (which carries its
|
|
204
|
+
content hash); the structural chain to the Source rides the PART_OF edge +
|
|
205
|
+
the start/end properties. `segment_path` (the source-codec cut) stays here as
|
|
206
|
+
a rendition-independent archival pointer.
|
|
177
207
|
"""
|
|
178
208
|
|
|
179
209
|
source: str # Owning Source node id
|
|
180
210
|
index: int # Position in the coarse spine (0-based)
|
|
181
211
|
start: float # Boundary start (source-coordinate seconds)
|
|
182
212
|
end: float # Boundary end (source-coordinate seconds)
|
|
183
|
-
|
|
184
|
-
model_input_hash: str # Content hash over that WAV
|
|
185
|
-
segment_path: Optional[str] # Source-codec cut file (archival pointer)
|
|
213
|
+
segment_path: Optional[str] # Source-codec cut file (rendition-independent archival pointer)
|
|
186
214
|
|
|
187
215
|
def id(self) -> str: # Deterministic node id
|
|
188
216
|
"""Deterministic node id."""
|
|
@@ -192,30 +220,69 @@ class AudioSegmentNode:
|
|
|
192
220
|
"Deterministic node id."
|
|
193
221
|
|
|
194
222
|
def to_graph_node(self) -> Dict[str, Any]: # Node wire dict
|
|
195
|
-
"""Build the AudioSegment node wire dict."""
|
|
223
|
+
"""Build the AudioSegment node wire dict (hashless boundary; sources empty)."""
|
|
196
224
|
props: Dict[str, Any] = {
|
|
197
|
-
"Build the AudioSegment node wire dict."
|
|
225
|
+
"Build the AudioSegment node wire dict (hashless boundary; sources empty)."
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
``` python
|
|
229
|
+
@dataclass
|
|
230
|
+
class AudioRenditionNode:
|
|
231
|
+
"""
|
|
232
|
+
A model-input rendition OF an AudioSegment — the materialized 16k-mono WAV
|
|
233
|
+
consumed by transcription/VAD/FA. OWNS the model-input (E14: the audio of
|
|
234
|
+
record); the AudioSegment above it is a hashless boundary.
|
|
235
|
+
|
|
236
|
+
Identity = (audio segment, preprocessing chain). The raw convert-only path is
|
|
237
|
+
an empty chain (its id is stable + chain-free); vocals isolation / future
|
|
238
|
+
speech-enhancement are non-empty chains. Raw + vocals are therefore distinct
|
|
239
|
+
renditions that COEXIST under one AudioSegment — the divergent model-input
|
|
240
|
+
hash that used to collide the AudioSegment now lives on distinct rendition
|
|
241
|
+
nodes. Connects UP to its AudioSegment by DERIVED_FROM (it is derived from the
|
|
242
|
+
segment's audio); the production act is recorded separately by a CR-18
|
|
243
|
+
`Derivation` event.
|
|
244
|
+
"""
|
|
245
|
+
|
|
246
|
+
audio_segment: str # Owning AudioSegment node id
|
|
247
|
+
model_input_path: str # The 16kHz mono WAV consumed by transcription/VAD/FA
|
|
248
|
+
model_input_hash: str # Content hash over that WAV (the rendition's identity-of-record artifact)
|
|
249
|
+
chain: List[str] = field(...) # Ordered preprocessing-chain descriptors; [] = raw convert-only
|
|
250
|
+
|
|
251
|
+
def id(self) -> str: # Deterministic node id
|
|
252
|
+
"""Deterministic node id."""
|
|
253
|
+
return audio_rendition_node_id(self.audio_segment, self.chain)
|
|
254
|
+
|
|
255
|
+
def to_graph_node(self) -> Dict[str, Any]: # Node wire dict
|
|
256
|
+
"Deterministic node id."
|
|
257
|
+
|
|
258
|
+
def to_graph_node(self) -> Dict[str, Any]: # Node wire dict
|
|
259
|
+
"""Build the AudioRendition node wire dict (owns the model-input SourceRef)."""
|
|
260
|
+
props: Dict[str, Any] = {
|
|
261
|
+
"Build the AudioRendition node wire dict (owns the model-input SourceRef)."
|
|
262
|
+
|
|
263
|
+
def derived_edge(self) -> Dict[str, Any]: # Edge wire dict
|
|
264
|
+
"DERIVED_FROM edge: this rendition derives from its AudioSegment's audio."
|
|
198
265
|
```
|
|
199
266
|
|
|
200
267
|
``` python
|
|
201
268
|
@dataclass
|
|
202
269
|
class TranscriptNode:
|
|
203
270
|
"""
|
|
204
|
-
One transcriber's text for one
|
|
271
|
+
One transcriber's text for one AudioRendition (per-transcriber variants at
|
|
205
272
|
the coarse layer — cross-transcriber divergence lives here, C4/C14).
|
|
206
273
|
"""
|
|
207
274
|
|
|
208
|
-
|
|
275
|
+
rendition: str # Owning AudioRendition node id
|
|
209
276
|
transcriber: str # Transcriber capability name
|
|
210
277
|
config_hash: str # Transcriber config hash
|
|
211
278
|
text: str # The transcript text (stored ONCE here; fine Segments slice into it)
|
|
212
|
-
audio_hash: str # Content hash of the consumed model-input WAV
|
|
279
|
+
audio_hash: str # Content hash of the consumed model-input WAV (the rendition's)
|
|
213
280
|
metadata: Dict[str, Any] = field(...) # Transcriber-reported metadata
|
|
214
281
|
asserted_at: Optional[float] # Derivation timestamp; None = now
|
|
215
282
|
|
|
216
283
|
def id(self) -> str: # Deterministic node id
|
|
217
284
|
"""Deterministic node id."""
|
|
218
|
-
return transcript_node_id(self.
|
|
285
|
+
return transcript_node_id(self.rendition, self.transcriber, self.config_hash)
|
|
219
286
|
|
|
220
287
|
def to_graph_node(self) -> Dict[str, Any]: # Node wire dict
|
|
221
288
|
"Deterministic node id."
|
|
@@ -226,7 +293,7 @@ class TranscriptNode:
|
|
|
226
293
|
"Build the Transcript node wire dict (capability attribution included)."
|
|
227
294
|
|
|
228
295
|
def derived_edge(self) -> Dict[str, Any]: # Edge wire dict
|
|
229
|
-
"DERIVED_FROM edge: this Transcript derives from its
|
|
296
|
+
"DERIVED_FROM edge: this Transcript derives from its AudioRendition."
|
|
230
297
|
```
|
|
231
298
|
|
|
232
299
|
``` python
|
|
@@ -252,7 +319,8 @@ class TranscriptSliceRef:
|
|
|
252
319
|
class SegmentNode:
|
|
253
320
|
"""
|
|
254
321
|
Fine spine member: one VAD chunk — IMMUTABLE audio range + CORRECTABLE
|
|
255
|
-
text (the immutable-audio/mutable-text spine seam).
|
|
322
|
+
text (the immutable-audio/mutable-text spine seam). PART_OF its AudioRendition
|
|
323
|
+
(each rendition has its own fine spine).
|
|
256
324
|
|
|
257
325
|
Layer-0 `text` is the ACCURACY transcriber's alignment; the designation is
|
|
258
326
|
per-segment provenance, not global config (`text_from` names the
|
|
@@ -260,26 +328,26 @@ class SegmentNode:
|
|
|
260
328
|
`text_slices`, the authoritative one included).
|
|
261
329
|
"""
|
|
262
330
|
|
|
263
|
-
|
|
331
|
+
rendition: str # Owning AudioRendition node id
|
|
264
332
|
vad_config_hash: str # VAD config hash (skeleton identity input)
|
|
265
|
-
chunk_start: float # VAD chunk start (chunk-local seconds within the
|
|
333
|
+
chunk_start: float # VAD chunk start (chunk-local seconds within the rendition WAV)
|
|
266
334
|
chunk_end: float # VAD chunk end (chunk-local seconds)
|
|
267
335
|
index: int # Source-wide fine-spine index (0-based)
|
|
268
336
|
start_time: float # Source-coordinate start (navigation)
|
|
269
337
|
end_time: float # Source-coordinate end
|
|
270
338
|
text: str = '' # Layer-0 text (accuracy alignment; "" = no aligned words, D14 class)
|
|
271
|
-
audio_hash: str = '' # Content hash of the owning
|
|
339
|
+
audio_hash: str = '' # Content hash of the owning rendition's model-input WAV
|
|
272
340
|
source: Optional[str] # Source node id (convenience for direct filters)
|
|
273
341
|
text_from: Optional[str] # Authoritative Transcript node id (None when text is empty)
|
|
274
342
|
text_slices: List[TranscriptSliceRef] = field(...) # All per-transcriber slice refs
|
|
275
343
|
|
|
276
344
|
def id(self) -> str: # Deterministic node id
|
|
277
|
-
"""Deterministic node id (audio-side identity; shared across transcribers)."""
|
|
278
|
-
return segment_node_id(self.
|
|
345
|
+
"""Deterministic node id (audio-side identity; shared across transcribers, per-rendition)."""
|
|
346
|
+
return segment_node_id(self.rendition, self.vad_config_hash,
|
|
279
347
|
self.chunk_start, self.chunk_end)
|
|
280
348
|
|
|
281
349
|
def to_graph_node(self) -> Dict[str, Any]: # Node wire dict
|
|
282
|
-
"Deterministic node id (audio-side identity; shared across transcribers)."
|
|
350
|
+
"Deterministic node id (audio-side identity; shared across transcribers, per-rendition)."
|
|
283
351
|
|
|
284
352
|
def to_graph_node(self) -> Dict[str, Any]: # Node wire dict
|
|
285
353
|
"""Build the Segment node wire dict (audio ref + per-transcriber text slice refs)."""
|
|
@@ -12,7 +12,7 @@ pip install cjm_transcript_graph_schema
|
|
|
12
12
|
## Project Structure
|
|
13
13
|
|
|
14
14
|
nbs/
|
|
15
|
-
└── schema.ipynb # The audio-transcript layer schema (where-graph-begins locked layer schema): `Source -> AudioSegment(coarse
|
|
15
|
+
└── schema.ipynb # The audio-transcript layer schema (where-graph-begins locked layer schema): `Source -> AudioSegment(coarse boundary) -> AudioRendition(model-input: raw | vocals | …) -> Transcript(per-transcriber variants)` emitted by transcription, extended with the fine `Segment` spine (per-rendition) by decomposition. Deterministic identity tuples per the stage-5 ratified rule; the `AudioRendition` node lets raw + preprocessed model-inputs of one boundary coexist in one graph. `Document` from the pre-CR-18 era dissolves into `Source`.
|
|
16
16
|
|
|
17
17
|
Total: 1 notebook
|
|
18
18
|
|
|
@@ -37,10 +37,12 @@ Detailed documentation for each module in the project:
|
|
|
37
37
|
|
|
38
38
|
> The audio-transcript layer schema (where-graph-begins locked layer
|
|
39
39
|
> schema):
|
|
40
|
-
> `Source -> AudioSegment(coarse
|
|
41
|
-
> emitted by transcription, extended with the fine `Segment` spine
|
|
42
|
-
> decomposition. Deterministic identity tuples per
|
|
43
|
-
> rule; `
|
|
40
|
+
> `Source -> AudioSegment(coarse boundary) -> AudioRendition(model-input: raw | vocals | …) -> Transcript(per-transcriber variants)`
|
|
41
|
+
> emitted by transcription, extended with the fine `Segment` spine
|
|
42
|
+
> (per-rendition) by decomposition. Deterministic identity tuples per
|
|
43
|
+
> the stage-5 ratified rule; the `AudioRendition` node lets raw +
|
|
44
|
+
> preprocessed model-inputs of one boundary coexist in one graph.
|
|
45
|
+
> `Document` from the pre-CR-18 era dissolves into `Source`.
|
|
44
46
|
|
|
45
47
|
#### Import
|
|
46
48
|
|
|
@@ -49,10 +51,12 @@ from cjm_transcript_graph_schema.schema import (
|
|
|
49
51
|
TranscriptGraphLabels,
|
|
50
52
|
source_node_id,
|
|
51
53
|
audio_segment_node_id,
|
|
54
|
+
audio_rendition_node_id,
|
|
52
55
|
transcript_node_id,
|
|
53
56
|
segment_node_id,
|
|
54
57
|
SourceNode,
|
|
55
58
|
AudioSegmentNode,
|
|
59
|
+
AudioRenditionNode,
|
|
56
60
|
TranscriptNode,
|
|
57
61
|
TranscriptSliceRef,
|
|
58
62
|
SegmentNode
|
|
@@ -77,36 +81,60 @@ def audio_segment_node_id(
|
|
|
77
81
|
"""
|
|
78
82
|
AudioSegment identity = (source, boundary range).
|
|
79
83
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
re-derivation reproduces
|
|
84
|
+
Rendition-independent by design: an AudioSegment is a BOUNDARY of the source
|
|
85
|
+
(an audio fact), shared across every model-input rendition of it. The
|
|
86
|
+
boundary computation is pure and deterministic, so re-derivation reproduces
|
|
87
|
+
the id; the model-input WAV lives on the `AudioRendition` child, not here.
|
|
83
88
|
"""
|
|
84
89
|
```
|
|
85
90
|
|
|
86
91
|
``` python
|
|
87
|
-
def
|
|
92
|
+
def audio_rendition_node_id(
|
|
88
93
|
audio_segment_id: str, # Owning AudioSegment node id
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
chain: List[str], # Ordered preprocessing-chain descriptors; [] = the raw convert-only rendition
|
|
95
|
+
) -> str: # Deterministic AudioRendition node id
|
|
96
|
+
"""
|
|
97
|
+
AudioRendition identity = (audio segment, preprocessing chain).
|
|
98
|
+
|
|
99
|
+
A rendition is one model-input audio OF an AudioSegment. The chain is the
|
|
100
|
+
ordered list of preprocessing steps that produced it (each an opaque
|
|
101
|
+
descriptor string, e.g. ``"source_separation:cjm-media-plugin-demucs@<cfg>"``);
|
|
102
|
+
an EMPTY chain is the raw convert-only rendition, whose id is therefore
|
|
103
|
+
stable and chain-free. The universal 16k-mono resample is implicit (not a
|
|
104
|
+
chain step) — so raw + vocals are distinct renditions under one shared
|
|
105
|
+
AudioSegment, and the layer's identity-mismatch check never collides them.
|
|
106
|
+
Re-derivable from the manifest chain alone (extenders recompute, never
|
|
107
|
+
search).
|
|
108
|
+
"""
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
``` python
|
|
112
|
+
def transcript_node_id(
|
|
113
|
+
rendition_id: str, # Owning AudioRendition node id
|
|
114
|
+
transcriber: str, # Transcriber capability name (e.g. "cjm-transcription-plugin-whisper")
|
|
115
|
+
config_hash: str, # Transcriber config hash
|
|
91
116
|
) -> str: # Deterministic Transcript node id
|
|
92
117
|
"""
|
|
93
|
-
Transcript identity = (audio
|
|
118
|
+
Transcript identity = (audio rendition, transcriber, config) — MIRRORS the
|
|
94
119
|
capability cache key UNIQUE(audio_path, config_hash), so the graph node is
|
|
95
|
-
the durable face of the cached row (the structural E13 fix).
|
|
120
|
+
the durable face of the cached row (the structural E13 fix). Keyed on the
|
|
121
|
+
RENDITION, so a raw vs vocals transcript of the same boundary are distinct.
|
|
96
122
|
"""
|
|
97
123
|
```
|
|
98
124
|
|
|
99
125
|
``` python
|
|
100
126
|
def segment_node_id(
|
|
101
|
-
|
|
102
|
-
vad_config_hash: str,
|
|
103
|
-
chunk_start: float,
|
|
104
|
-
chunk_end: float,
|
|
127
|
+
rendition_id: str, # Owning AudioRendition node id
|
|
128
|
+
vad_config_hash: str, # VAD capability config hash (skeleton identity input)
|
|
129
|
+
chunk_start: float, # VAD chunk start (chunk-local seconds within the rendition WAV)
|
|
130
|
+
chunk_end: float, # VAD chunk end (chunk-local seconds)
|
|
105
131
|
) -> str: # Deterministic Segment node id
|
|
106
132
|
"""
|
|
107
|
-
Fine Segment identity = audio-side only (audio
|
|
133
|
+
Fine Segment identity = audio-side only (audio rendition, VAD config, chunk
|
|
108
134
|
range) — so the skeleton's ids are SHARED across transcribers by
|
|
109
|
-
construction (C4 "store agreement once" falls out of identity design).
|
|
135
|
+
construction (C4 "store agreement once" falls out of identity design). Keyed
|
|
136
|
+
on the RENDITION: each rendition has its own fine spine (vocals isolation can
|
|
137
|
+
yield different VAD chunk boundaries than raw).
|
|
110
138
|
"""
|
|
111
139
|
```
|
|
112
140
|
|
|
@@ -148,23 +176,23 @@ class SourceNode:
|
|
|
148
176
|
@dataclass
|
|
149
177
|
class AudioSegmentNode:
|
|
150
178
|
"""
|
|
151
|
-
Coarse ~5-min spine member
|
|
152
|
-
|
|
179
|
+
Coarse ~5-min spine member: a BOUNDARY range of the Source (an audio fact),
|
|
180
|
+
shared across every model-input rendition of it.
|
|
153
181
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
182
|
+
Hashless by design: a boundary is not a materialized artifact — a hash over
|
|
183
|
+
the sliced ORIGINAL source bytes is not materializable without extracting
|
|
184
|
+
per-range artifacts (decoded ranges are not byte ranges). The materialized
|
|
185
|
+
model-input WAV lives on the `AudioRendition` child (which carries its
|
|
186
|
+
content hash); the structural chain to the Source rides the PART_OF edge +
|
|
187
|
+
the start/end properties. `segment_path` (the source-codec cut) stays here as
|
|
188
|
+
a rendition-independent archival pointer.
|
|
159
189
|
"""
|
|
160
190
|
|
|
161
191
|
source: str # Owning Source node id
|
|
162
192
|
index: int # Position in the coarse spine (0-based)
|
|
163
193
|
start: float # Boundary start (source-coordinate seconds)
|
|
164
194
|
end: float # Boundary end (source-coordinate seconds)
|
|
165
|
-
|
|
166
|
-
model_input_hash: str # Content hash over that WAV
|
|
167
|
-
segment_path: Optional[str] # Source-codec cut file (archival pointer)
|
|
195
|
+
segment_path: Optional[str] # Source-codec cut file (rendition-independent archival pointer)
|
|
168
196
|
|
|
169
197
|
def id(self) -> str: # Deterministic node id
|
|
170
198
|
"""Deterministic node id."""
|
|
@@ -174,30 +202,69 @@ class AudioSegmentNode:
|
|
|
174
202
|
"Deterministic node id."
|
|
175
203
|
|
|
176
204
|
def to_graph_node(self) -> Dict[str, Any]: # Node wire dict
|
|
177
|
-
"""Build the AudioSegment node wire dict."""
|
|
205
|
+
"""Build the AudioSegment node wire dict (hashless boundary; sources empty)."""
|
|
178
206
|
props: Dict[str, Any] = {
|
|
179
|
-
"Build the AudioSegment node wire dict."
|
|
207
|
+
"Build the AudioSegment node wire dict (hashless boundary; sources empty)."
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
``` python
|
|
211
|
+
@dataclass
|
|
212
|
+
class AudioRenditionNode:
|
|
213
|
+
"""
|
|
214
|
+
A model-input rendition OF an AudioSegment — the materialized 16k-mono WAV
|
|
215
|
+
consumed by transcription/VAD/FA. OWNS the model-input (E14: the audio of
|
|
216
|
+
record); the AudioSegment above it is a hashless boundary.
|
|
217
|
+
|
|
218
|
+
Identity = (audio segment, preprocessing chain). The raw convert-only path is
|
|
219
|
+
an empty chain (its id is stable + chain-free); vocals isolation / future
|
|
220
|
+
speech-enhancement are non-empty chains. Raw + vocals are therefore distinct
|
|
221
|
+
renditions that COEXIST under one AudioSegment — the divergent model-input
|
|
222
|
+
hash that used to collide the AudioSegment now lives on distinct rendition
|
|
223
|
+
nodes. Connects UP to its AudioSegment by DERIVED_FROM (it is derived from the
|
|
224
|
+
segment's audio); the production act is recorded separately by a CR-18
|
|
225
|
+
`Derivation` event.
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
audio_segment: str # Owning AudioSegment node id
|
|
229
|
+
model_input_path: str # The 16kHz mono WAV consumed by transcription/VAD/FA
|
|
230
|
+
model_input_hash: str # Content hash over that WAV (the rendition's identity-of-record artifact)
|
|
231
|
+
chain: List[str] = field(...) # Ordered preprocessing-chain descriptors; [] = raw convert-only
|
|
232
|
+
|
|
233
|
+
def id(self) -> str: # Deterministic node id
|
|
234
|
+
"""Deterministic node id."""
|
|
235
|
+
return audio_rendition_node_id(self.audio_segment, self.chain)
|
|
236
|
+
|
|
237
|
+
def to_graph_node(self) -> Dict[str, Any]: # Node wire dict
|
|
238
|
+
"Deterministic node id."
|
|
239
|
+
|
|
240
|
+
def to_graph_node(self) -> Dict[str, Any]: # Node wire dict
|
|
241
|
+
"""Build the AudioRendition node wire dict (owns the model-input SourceRef)."""
|
|
242
|
+
props: Dict[str, Any] = {
|
|
243
|
+
"Build the AudioRendition node wire dict (owns the model-input SourceRef)."
|
|
244
|
+
|
|
245
|
+
def derived_edge(self) -> Dict[str, Any]: # Edge wire dict
|
|
246
|
+
"DERIVED_FROM edge: this rendition derives from its AudioSegment's audio."
|
|
180
247
|
```
|
|
181
248
|
|
|
182
249
|
``` python
|
|
183
250
|
@dataclass
|
|
184
251
|
class TranscriptNode:
|
|
185
252
|
"""
|
|
186
|
-
One transcriber's text for one
|
|
253
|
+
One transcriber's text for one AudioRendition (per-transcriber variants at
|
|
187
254
|
the coarse layer — cross-transcriber divergence lives here, C4/C14).
|
|
188
255
|
"""
|
|
189
256
|
|
|
190
|
-
|
|
257
|
+
rendition: str # Owning AudioRendition node id
|
|
191
258
|
transcriber: str # Transcriber capability name
|
|
192
259
|
config_hash: str # Transcriber config hash
|
|
193
260
|
text: str # The transcript text (stored ONCE here; fine Segments slice into it)
|
|
194
|
-
audio_hash: str # Content hash of the consumed model-input WAV
|
|
261
|
+
audio_hash: str # Content hash of the consumed model-input WAV (the rendition's)
|
|
195
262
|
metadata: Dict[str, Any] = field(...) # Transcriber-reported metadata
|
|
196
263
|
asserted_at: Optional[float] # Derivation timestamp; None = now
|
|
197
264
|
|
|
198
265
|
def id(self) -> str: # Deterministic node id
|
|
199
266
|
"""Deterministic node id."""
|
|
200
|
-
return transcript_node_id(self.
|
|
267
|
+
return transcript_node_id(self.rendition, self.transcriber, self.config_hash)
|
|
201
268
|
|
|
202
269
|
def to_graph_node(self) -> Dict[str, Any]: # Node wire dict
|
|
203
270
|
"Deterministic node id."
|
|
@@ -208,7 +275,7 @@ class TranscriptNode:
|
|
|
208
275
|
"Build the Transcript node wire dict (capability attribution included)."
|
|
209
276
|
|
|
210
277
|
def derived_edge(self) -> Dict[str, Any]: # Edge wire dict
|
|
211
|
-
"DERIVED_FROM edge: this Transcript derives from its
|
|
278
|
+
"DERIVED_FROM edge: this Transcript derives from its AudioRendition."
|
|
212
279
|
```
|
|
213
280
|
|
|
214
281
|
``` python
|
|
@@ -234,7 +301,8 @@ class TranscriptSliceRef:
|
|
|
234
301
|
class SegmentNode:
|
|
235
302
|
"""
|
|
236
303
|
Fine spine member: one VAD chunk — IMMUTABLE audio range + CORRECTABLE
|
|
237
|
-
text (the immutable-audio/mutable-text spine seam).
|
|
304
|
+
text (the immutable-audio/mutable-text spine seam). PART_OF its AudioRendition
|
|
305
|
+
(each rendition has its own fine spine).
|
|
238
306
|
|
|
239
307
|
Layer-0 `text` is the ACCURACY transcriber's alignment; the designation is
|
|
240
308
|
per-segment provenance, not global config (`text_from` names the
|
|
@@ -242,26 +310,26 @@ class SegmentNode:
|
|
|
242
310
|
`text_slices`, the authoritative one included).
|
|
243
311
|
"""
|
|
244
312
|
|
|
245
|
-
|
|
313
|
+
rendition: str # Owning AudioRendition node id
|
|
246
314
|
vad_config_hash: str # VAD config hash (skeleton identity input)
|
|
247
|
-
chunk_start: float # VAD chunk start (chunk-local seconds within the
|
|
315
|
+
chunk_start: float # VAD chunk start (chunk-local seconds within the rendition WAV)
|
|
248
316
|
chunk_end: float # VAD chunk end (chunk-local seconds)
|
|
249
317
|
index: int # Source-wide fine-spine index (0-based)
|
|
250
318
|
start_time: float # Source-coordinate start (navigation)
|
|
251
319
|
end_time: float # Source-coordinate end
|
|
252
320
|
text: str = '' # Layer-0 text (accuracy alignment; "" = no aligned words, D14 class)
|
|
253
|
-
audio_hash: str = '' # Content hash of the owning
|
|
321
|
+
audio_hash: str = '' # Content hash of the owning rendition's model-input WAV
|
|
254
322
|
source: Optional[str] # Source node id (convenience for direct filters)
|
|
255
323
|
text_from: Optional[str] # Authoritative Transcript node id (None when text is empty)
|
|
256
324
|
text_slices: List[TranscriptSliceRef] = field(...) # All per-transcriber slice refs
|
|
257
325
|
|
|
258
326
|
def id(self) -> str: # Deterministic node id
|
|
259
|
-
"""Deterministic node id (audio-side identity; shared across transcribers)."""
|
|
260
|
-
return segment_node_id(self.
|
|
327
|
+
"""Deterministic node id (audio-side identity; shared across transcribers, per-rendition)."""
|
|
328
|
+
return segment_node_id(self.rendition, self.vad_config_hash,
|
|
261
329
|
self.chunk_start, self.chunk_end)
|
|
262
330
|
|
|
263
331
|
def to_graph_node(self) -> Dict[str, Any]: # Node wire dict
|
|
264
|
-
"Deterministic node id (audio-side identity; shared across transcribers)."
|
|
332
|
+
"Deterministic node id (audio-side identity; shared across transcribers, per-rendition)."
|
|
265
333
|
|
|
266
334
|
def to_graph_node(self) -> Dict[str, Any]: # Node wire dict
|
|
267
335
|
"""Build the Segment node wire dict (audio ref + per-transcriber text slice refs)."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.7"
|
|
@@ -5,7 +5,15 @@ d = { 'settings': { 'branch': 'main',
|
|
|
5
5
|
'doc_host': 'https://cj-mills.github.io',
|
|
6
6
|
'git_url': 'https://github.com/cj-mills/cjm-transcript-graph-schema',
|
|
7
7
|
'lib_path': 'cjm_transcript_graph_schema'},
|
|
8
|
-
'syms': { 'cjm_transcript_graph_schema.schema': { 'cjm_transcript_graph_schema.schema.
|
|
8
|
+
'syms': { 'cjm_transcript_graph_schema.schema': { 'cjm_transcript_graph_schema.schema.AudioRenditionNode': ( 'schema.html#audiorenditionnode',
|
|
9
|
+
'cjm_transcript_graph_schema/schema.py'),
|
|
10
|
+
'cjm_transcript_graph_schema.schema.AudioRenditionNode.derived_edge': ( 'schema.html#audiorenditionnode.derived_edge',
|
|
11
|
+
'cjm_transcript_graph_schema/schema.py'),
|
|
12
|
+
'cjm_transcript_graph_schema.schema.AudioRenditionNode.id': ( 'schema.html#audiorenditionnode.id',
|
|
13
|
+
'cjm_transcript_graph_schema/schema.py'),
|
|
14
|
+
'cjm_transcript_graph_schema.schema.AudioRenditionNode.to_graph_node': ( 'schema.html#audiorenditionnode.to_graph_node',
|
|
15
|
+
'cjm_transcript_graph_schema/schema.py'),
|
|
16
|
+
'cjm_transcript_graph_schema.schema.AudioSegmentNode': ( 'schema.html#audiosegmentnode',
|
|
9
17
|
'cjm_transcript_graph_schema/schema.py'),
|
|
10
18
|
'cjm_transcript_graph_schema.schema.AudioSegmentNode.id': ( 'schema.html#audiosegmentnode.id',
|
|
11
19
|
'cjm_transcript_graph_schema/schema.py'),
|
|
@@ -39,6 +47,8 @@ d = { 'settings': { 'branch': 'main',
|
|
|
39
47
|
'cjm_transcript_graph_schema/schema.py'),
|
|
40
48
|
'cjm_transcript_graph_schema.schema.TranscriptSliceRef.to_source_ref': ( 'schema.html#transcriptsliceref.to_source_ref',
|
|
41
49
|
'cjm_transcript_graph_schema/schema.py'),
|
|
50
|
+
'cjm_transcript_graph_schema.schema.audio_rendition_node_id': ( 'schema.html#audio_rendition_node_id',
|
|
51
|
+
'cjm_transcript_graph_schema/schema.py'),
|
|
42
52
|
'cjm_transcript_graph_schema.schema.audio_segment_node_id': ( 'schema.html#audio_segment_node_id',
|
|
43
53
|
'cjm_transcript_graph_schema/schema.py'),
|
|
44
54
|
'cjm_transcript_graph_schema.schema.segment_node_id': ( 'schema.html#segment_node_id',
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
"""The audio-transcript layer schema (where-graph-begins locked layer schema): `Source -> AudioSegment(coarse
|
|
1
|
+
"""The audio-transcript layer schema (where-graph-begins locked layer schema): `Source -> AudioSegment(coarse boundary) -> AudioRendition(model-input: raw | vocals | …) -> Transcript(per-transcriber variants)` emitted by transcription, extended with the fine `Segment` spine (per-rendition) by decomposition. Deterministic identity tuples per the stage-5 ratified rule; the `AudioRendition` node lets raw + preprocessed model-inputs of one boundary coexist in one graph. `Document` from the pre-CR-18 era dissolves into `Source`.
|
|
2
2
|
|
|
3
3
|
Docs: https://cj-mills.github.io/cjm-transcript-graph-schemaschema.html.md"""
|
|
4
4
|
|
|
5
5
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/schema.ipynb.
|
|
6
6
|
|
|
7
7
|
# %% auto #0
|
|
8
|
-
__all__ = ['TranscriptGraphLabels', 'source_node_id', 'audio_segment_node_id', '
|
|
9
|
-
'
|
|
8
|
+
__all__ = ['TranscriptGraphLabels', 'source_node_id', 'audio_segment_node_id', 'audio_rendition_node_id', 'transcript_node_id',
|
|
9
|
+
'segment_node_id', 'SourceNode', 'AudioSegmentNode', 'AudioRenditionNode', 'TranscriptNode',
|
|
10
|
+
'TranscriptSliceRef', 'SegmentNode']
|
|
10
11
|
|
|
11
12
|
# %% ../nbs/schema.ipynb #76d0dd1c
|
|
12
13
|
from pathlib import Path
|
|
@@ -22,15 +23,16 @@ from cjm_context_graph_primitives.provenance import SourceRef
|
|
|
22
23
|
# %% ../nbs/schema.ipynb #ff94773a
|
|
23
24
|
class TranscriptGraphLabels:
|
|
24
25
|
"""Node labels of the audio-transcript layer schema."""
|
|
25
|
-
SOURCE = "Source"
|
|
26
|
-
AUDIO_SEGMENT = "AudioSegment"
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
SOURCE = "Source" # Provenance root: one ingested media file
|
|
27
|
+
AUDIO_SEGMENT = "AudioSegment" # Coarse ~5-min spine member: a boundary range of the Source (an audio FACT; rendition-independent)
|
|
28
|
+
AUDIO_RENDITION = "AudioRendition" # A model-input rendition OF an AudioSegment (raw convert | vocals | future enhancement); OWNS the model-input WAV
|
|
29
|
+
TRANSCRIPT = "Transcript" # Per-transcriber text variant of one AudioRendition
|
|
30
|
+
SEGMENT = "Segment" # Fine spine member: one VAD chunk (immutable audio + correctable text), per-rendition
|
|
29
31
|
|
|
30
32
|
@classmethod
|
|
31
33
|
def all(cls) -> list: # All schema labels
|
|
32
34
|
"""All schema labels."""
|
|
33
|
-
return [cls.SOURCE, cls.AUDIO_SEGMENT, cls.TRANSCRIPT, cls.SEGMENT]
|
|
35
|
+
return [cls.SOURCE, cls.AUDIO_SEGMENT, cls.AUDIO_RENDITION, cls.TRANSCRIPT, cls.SEGMENT]
|
|
34
36
|
|
|
35
37
|
# %% ../nbs/schema.ipynb #a5ea12db
|
|
36
38
|
def source_node_id(
|
|
@@ -47,33 +49,55 @@ def audio_segment_node_id(
|
|
|
47
49
|
) -> str: # Deterministic AudioSegment node id
|
|
48
50
|
"""AudioSegment identity = (source, boundary range).
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
re-derivation reproduces
|
|
52
|
+
Rendition-independent by design: an AudioSegment is a BOUNDARY of the source
|
|
53
|
+
(an audio fact), shared across every model-input rendition of it. The
|
|
54
|
+
boundary computation is pure and deterministic, so re-derivation reproduces
|
|
55
|
+
the id; the model-input WAV lives on the `AudioRendition` child, not here."""
|
|
53
56
|
return derive_node_id("audio-segment", source_id, start, end)
|
|
54
57
|
|
|
58
|
+
# %% ../nbs/schema.ipynb #8e147ac9
|
|
59
|
+
def audio_rendition_node_id(
|
|
60
|
+
audio_segment_id: str, # Owning AudioSegment node id
|
|
61
|
+
chain: List[str], # Ordered preprocessing-chain descriptors; [] = the raw convert-only rendition
|
|
62
|
+
) -> str: # Deterministic AudioRendition node id
|
|
63
|
+
"""AudioRendition identity = (audio segment, preprocessing chain).
|
|
64
|
+
|
|
65
|
+
A rendition is one model-input audio OF an AudioSegment. The chain is the
|
|
66
|
+
ordered list of preprocessing steps that produced it (each an opaque
|
|
67
|
+
descriptor string, e.g. ``"source_separation:cjm-media-plugin-demucs@<cfg>"``);
|
|
68
|
+
an EMPTY chain is the raw convert-only rendition, whose id is therefore
|
|
69
|
+
stable and chain-free. The universal 16k-mono resample is implicit (not a
|
|
70
|
+
chain step) — so raw + vocals are distinct renditions under one shared
|
|
71
|
+
AudioSegment, and the layer's identity-mismatch check never collides them.
|
|
72
|
+
Re-derivable from the manifest chain alone (extenders recompute, never
|
|
73
|
+
search)."""
|
|
74
|
+
return derive_node_id("audio-rendition", audio_segment_id, *chain)
|
|
75
|
+
|
|
55
76
|
# %% ../nbs/schema.ipynb #6fe30fc8
|
|
56
77
|
def transcript_node_id(
|
|
57
|
-
|
|
58
|
-
transcriber: str,
|
|
59
|
-
config_hash: str,
|
|
78
|
+
rendition_id: str, # Owning AudioRendition node id
|
|
79
|
+
transcriber: str, # Transcriber capability name (e.g. "cjm-transcription-plugin-whisper")
|
|
80
|
+
config_hash: str, # Transcriber config hash
|
|
60
81
|
) -> str: # Deterministic Transcript node id
|
|
61
|
-
"""Transcript identity = (audio
|
|
82
|
+
"""Transcript identity = (audio rendition, transcriber, config) — MIRRORS the
|
|
62
83
|
capability cache key UNIQUE(audio_path, config_hash), so the graph node is
|
|
63
|
-
the durable face of the cached row (the structural E13 fix).
|
|
64
|
-
|
|
84
|
+
the durable face of the cached row (the structural E13 fix). Keyed on the
|
|
85
|
+
RENDITION, so a raw vs vocals transcript of the same boundary are distinct."""
|
|
86
|
+
return derive_node_id("transcript", rendition_id, transcriber, config_hash)
|
|
65
87
|
|
|
66
88
|
# %% ../nbs/schema.ipynb #f0e4f510
|
|
67
89
|
def segment_node_id(
|
|
68
|
-
|
|
69
|
-
vad_config_hash: str,
|
|
70
|
-
chunk_start: float,
|
|
71
|
-
chunk_end: float,
|
|
90
|
+
rendition_id: str, # Owning AudioRendition node id
|
|
91
|
+
vad_config_hash: str, # VAD capability config hash (skeleton identity input)
|
|
92
|
+
chunk_start: float, # VAD chunk start (chunk-local seconds within the rendition WAV)
|
|
93
|
+
chunk_end: float, # VAD chunk end (chunk-local seconds)
|
|
72
94
|
) -> str: # Deterministic Segment node id
|
|
73
|
-
"""Fine Segment identity = audio-side only (audio
|
|
95
|
+
"""Fine Segment identity = audio-side only (audio rendition, VAD config, chunk
|
|
74
96
|
range) — so the skeleton's ids are SHARED across transcribers by
|
|
75
|
-
construction (C4 "store agreement once" falls out of identity design).
|
|
76
|
-
|
|
97
|
+
construction (C4 "store agreement once" falls out of identity design). Keyed
|
|
98
|
+
on the RENDITION: each rendition has its own fine spine (vocals isolation can
|
|
99
|
+
yield different VAD chunk boundaries than raw)."""
|
|
100
|
+
return derive_node_id("segment", rendition_id, vad_config_hash, chunk_start, chunk_end)
|
|
77
101
|
|
|
78
102
|
# %% ../nbs/schema.ipynb #d768584a
|
|
79
103
|
@dataclass
|
|
@@ -107,21 +131,21 @@ class SourceNode:
|
|
|
107
131
|
# %% ../nbs/schema.ipynb #3d76c961
|
|
108
132
|
@dataclass
|
|
109
133
|
class AudioSegmentNode:
|
|
110
|
-
"""Coarse ~5-min spine member
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
134
|
+
"""Coarse ~5-min spine member: a BOUNDARY range of the Source (an audio fact),
|
|
135
|
+
shared across every model-input rendition of it.
|
|
136
|
+
|
|
137
|
+
Hashless by design: a boundary is not a materialized artifact — a hash over
|
|
138
|
+
the sliced ORIGINAL source bytes is not materializable without extracting
|
|
139
|
+
per-range artifacts (decoded ranges are not byte ranges). The materialized
|
|
140
|
+
model-input WAV lives on the `AudioRendition` child (which carries its
|
|
141
|
+
content hash); the structural chain to the Source rides the PART_OF edge +
|
|
142
|
+
the start/end properties. `segment_path` (the source-codec cut) stays here as
|
|
143
|
+
a rendition-independent archival pointer."""
|
|
118
144
|
source: str # Owning Source node id
|
|
119
145
|
index: int # Position in the coarse spine (0-based)
|
|
120
146
|
start: float # Boundary start (source-coordinate seconds)
|
|
121
147
|
end: float # Boundary end (source-coordinate seconds)
|
|
122
|
-
|
|
123
|
-
model_input_hash: str # Content hash over that WAV
|
|
124
|
-
segment_path: Optional[str] = None # Source-codec cut file (archival pointer)
|
|
148
|
+
segment_path: Optional[str] = None # Source-codec cut file (rendition-independent archival pointer)
|
|
125
149
|
|
|
126
150
|
@property
|
|
127
151
|
def id(self) -> str: # Deterministic node id
|
|
@@ -129,12 +153,11 @@ class AudioSegmentNode:
|
|
|
129
153
|
return audio_segment_node_id(self.source, self.start, self.end)
|
|
130
154
|
|
|
131
155
|
def to_graph_node(self) -> Dict[str, Any]: # Node wire dict
|
|
132
|
-
"""Build the AudioSegment node wire dict."""
|
|
156
|
+
"""Build the AudioSegment node wire dict (hashless boundary; sources empty)."""
|
|
133
157
|
props: Dict[str, Any] = {
|
|
134
158
|
"index": self.index,
|
|
135
159
|
"start": self.start,
|
|
136
160
|
"end": self.end,
|
|
137
|
-
"model_input_path": self.model_input_path,
|
|
138
161
|
"source_id": self.source,
|
|
139
162
|
}
|
|
140
163
|
if self.segment_path:
|
|
@@ -143,27 +166,73 @@ class AudioSegmentNode:
|
|
|
143
166
|
"id": self.id,
|
|
144
167
|
"label": TranscriptGraphLabels.AUDIO_SEGMENT,
|
|
145
168
|
"properties": props,
|
|
169
|
+
"sources": [],
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
# %% ../nbs/schema.ipynb #c97ae502
|
|
173
|
+
@dataclass
|
|
174
|
+
class AudioRenditionNode:
|
|
175
|
+
"""A model-input rendition OF an AudioSegment — the materialized 16k-mono WAV
|
|
176
|
+
consumed by transcription/VAD/FA. OWNS the model-input (E14: the audio of
|
|
177
|
+
record); the AudioSegment above it is a hashless boundary.
|
|
178
|
+
|
|
179
|
+
Identity = (audio segment, preprocessing chain). The raw convert-only path is
|
|
180
|
+
an empty chain (its id is stable + chain-free); vocals isolation / future
|
|
181
|
+
speech-enhancement are non-empty chains. Raw + vocals are therefore distinct
|
|
182
|
+
renditions that COEXIST under one AudioSegment — the divergent model-input
|
|
183
|
+
hash that used to collide the AudioSegment now lives on distinct rendition
|
|
184
|
+
nodes. Connects UP to its AudioSegment by DERIVED_FROM (it is derived from the
|
|
185
|
+
segment's audio); the production act is recorded separately by a CR-18
|
|
186
|
+
`Derivation` event."""
|
|
187
|
+
audio_segment: str # Owning AudioSegment node id
|
|
188
|
+
model_input_path: str # The 16kHz mono WAV consumed by transcription/VAD/FA
|
|
189
|
+
model_input_hash: str # Content hash over that WAV (the rendition's identity-of-record artifact)
|
|
190
|
+
chain: List[str] = field(default_factory=list) # Ordered preprocessing-chain descriptors; [] = raw convert-only
|
|
191
|
+
|
|
192
|
+
@property
|
|
193
|
+
def id(self) -> str: # Deterministic node id
|
|
194
|
+
"""Deterministic node id."""
|
|
195
|
+
return audio_rendition_node_id(self.audio_segment, self.chain)
|
|
196
|
+
|
|
197
|
+
def to_graph_node(self) -> Dict[str, Any]: # Node wire dict
|
|
198
|
+
"""Build the AudioRendition node wire dict (owns the model-input SourceRef)."""
|
|
199
|
+
props: Dict[str, Any] = {
|
|
200
|
+
"audio_segment_id": self.audio_segment,
|
|
201
|
+
"model_input_path": self.model_input_path,
|
|
202
|
+
"chain": list(self.chain),
|
|
203
|
+
"is_raw": not self.chain,
|
|
204
|
+
}
|
|
205
|
+
if self.chain:
|
|
206
|
+
props["preprocessing"] = " | ".join(self.chain) # readable display label (the retired interim property)
|
|
207
|
+
return {
|
|
208
|
+
"id": self.id,
|
|
209
|
+
"label": TranscriptGraphLabels.AUDIO_RENDITION,
|
|
210
|
+
"properties": props,
|
|
146
211
|
"sources": [SourceRef(locator=FileRef(path=self.model_input_path),
|
|
147
212
|
content_hash=self.model_input_hash).to_dict()],
|
|
148
213
|
}
|
|
149
214
|
|
|
215
|
+
def derived_edge(self) -> Dict[str, Any]: # Edge wire dict
|
|
216
|
+
"""DERIVED_FROM edge: this rendition derives from its AudioSegment's audio."""
|
|
217
|
+
return make_edge(self.id, self.audio_segment, OverlayRelations.DERIVED_FROM)
|
|
218
|
+
|
|
150
219
|
# %% ../nbs/schema.ipynb #9948f2c5
|
|
151
220
|
@dataclass
|
|
152
221
|
class TranscriptNode:
|
|
153
|
-
"""One transcriber's text for one
|
|
222
|
+
"""One transcriber's text for one AudioRendition (per-transcriber variants at
|
|
154
223
|
the coarse layer — cross-transcriber divergence lives here, C4/C14)."""
|
|
155
|
-
|
|
224
|
+
rendition: str # Owning AudioRendition node id
|
|
156
225
|
transcriber: str # Transcriber capability name
|
|
157
226
|
config_hash: str # Transcriber config hash
|
|
158
227
|
text: str # The transcript text (stored ONCE here; fine Segments slice into it)
|
|
159
|
-
audio_hash: str # Content hash of the consumed model-input WAV
|
|
228
|
+
audio_hash: str # Content hash of the consumed model-input WAV (the rendition's)
|
|
160
229
|
metadata: Dict[str, Any] = field(default_factory=dict) # Transcriber-reported metadata
|
|
161
230
|
asserted_at: Optional[float] = None # Derivation timestamp; None = now
|
|
162
231
|
|
|
163
232
|
@property
|
|
164
233
|
def id(self) -> str: # Deterministic node id
|
|
165
234
|
"""Deterministic node id."""
|
|
166
|
-
return transcript_node_id(self.
|
|
235
|
+
return transcript_node_id(self.rendition, self.transcriber, self.config_hash)
|
|
167
236
|
|
|
168
237
|
def to_graph_node(self) -> Dict[str, Any]: # Node wire dict
|
|
169
238
|
"""Build the Transcript node wire dict (capability attribution included)."""
|
|
@@ -171,7 +240,7 @@ class TranscriptNode:
|
|
|
171
240
|
"transcriber": self.transcriber,
|
|
172
241
|
"config_hash": self.config_hash,
|
|
173
242
|
"text": self.text,
|
|
174
|
-
"
|
|
243
|
+
"rendition_id": self.rendition,
|
|
175
244
|
}
|
|
176
245
|
if self.metadata:
|
|
177
246
|
props["metadata"] = dict(self.metadata)
|
|
@@ -181,14 +250,14 @@ class TranscriptNode:
|
|
|
181
250
|
"id": self.id,
|
|
182
251
|
"label": TranscriptGraphLabels.TRANSCRIPT,
|
|
183
252
|
"properties": props,
|
|
184
|
-
"sources": [SourceRef(locator=GraphNodeRef(node_id=self.
|
|
253
|
+
"sources": [SourceRef(locator=GraphNodeRef(node_id=self.rendition),
|
|
185
254
|
content_hash=self.audio_hash,
|
|
186
255
|
slice=FullContent("audio")).to_dict()],
|
|
187
256
|
}
|
|
188
257
|
|
|
189
258
|
def derived_edge(self) -> Dict[str, Any]: # Edge wire dict
|
|
190
|
-
"""DERIVED_FROM edge: this Transcript derives from its
|
|
191
|
-
return make_edge(self.id, self.
|
|
259
|
+
"""DERIVED_FROM edge: this Transcript derives from its AudioRendition."""
|
|
260
|
+
return make_edge(self.id, self.rendition, OverlayRelations.DERIVED_FROM)
|
|
192
261
|
|
|
193
262
|
# %% ../nbs/schema.ipynb #0c830160
|
|
194
263
|
@dataclass
|
|
@@ -211,29 +280,30 @@ class TranscriptSliceRef:
|
|
|
211
280
|
@dataclass
|
|
212
281
|
class SegmentNode:
|
|
213
282
|
"""Fine spine member: one VAD chunk — IMMUTABLE audio range + CORRECTABLE
|
|
214
|
-
text (the immutable-audio/mutable-text spine seam).
|
|
283
|
+
text (the immutable-audio/mutable-text spine seam). PART_OF its AudioRendition
|
|
284
|
+
(each rendition has its own fine spine).
|
|
215
285
|
|
|
216
286
|
Layer-0 `text` is the ACCURACY transcriber's alignment; the designation is
|
|
217
287
|
per-segment provenance, not global config (`text_from` names the
|
|
218
288
|
authoritative Transcript; every transcriber's char range rides
|
|
219
289
|
`text_slices`, the authoritative one included)."""
|
|
220
|
-
|
|
290
|
+
rendition: str # Owning AudioRendition node id
|
|
221
291
|
vad_config_hash: str # VAD config hash (skeleton identity input)
|
|
222
|
-
chunk_start: float # VAD chunk start (chunk-local seconds within the
|
|
292
|
+
chunk_start: float # VAD chunk start (chunk-local seconds within the rendition WAV)
|
|
223
293
|
chunk_end: float # VAD chunk end (chunk-local seconds)
|
|
224
294
|
index: int # Source-wide fine-spine index (0-based)
|
|
225
295
|
start_time: float # Source-coordinate start (navigation)
|
|
226
296
|
end_time: float # Source-coordinate end
|
|
227
297
|
text: str = "" # Layer-0 text (accuracy alignment; "" = no aligned words, D14 class)
|
|
228
|
-
audio_hash: str = "" # Content hash of the owning
|
|
298
|
+
audio_hash: str = "" # Content hash of the owning rendition's model-input WAV
|
|
229
299
|
source: Optional[str] = None # Source node id (convenience for direct filters)
|
|
230
300
|
text_from: Optional[str] = None # Authoritative Transcript node id (None when text is empty)
|
|
231
301
|
text_slices: List[TranscriptSliceRef] = field(default_factory=list) # All per-transcriber slice refs
|
|
232
302
|
|
|
233
303
|
@property
|
|
234
304
|
def id(self) -> str: # Deterministic node id
|
|
235
|
-
"""Deterministic node id (audio-side identity; shared across transcribers)."""
|
|
236
|
-
return segment_node_id(self.
|
|
305
|
+
"""Deterministic node id (audio-side identity; shared across transcribers, per-rendition)."""
|
|
306
|
+
return segment_node_id(self.rendition, self.vad_config_hash,
|
|
237
307
|
self.chunk_start, self.chunk_end)
|
|
238
308
|
|
|
239
309
|
def to_graph_node(self) -> Dict[str, Any]: # Node wire dict
|
|
@@ -245,13 +315,13 @@ class SegmentNode:
|
|
|
245
315
|
"end_time": self.end_time,
|
|
246
316
|
"chunk_start": self.chunk_start,
|
|
247
317
|
"chunk_end": self.chunk_end,
|
|
248
|
-
"
|
|
318
|
+
"rendition_id": self.rendition,
|
|
249
319
|
}
|
|
250
320
|
if self.source:
|
|
251
321
|
props["source_id"] = self.source
|
|
252
322
|
if self.text_from:
|
|
253
323
|
props["text_from"] = self.text_from
|
|
254
|
-
sources = [SourceRef(locator=GraphNodeRef(node_id=self.
|
|
324
|
+
sources = [SourceRef(locator=GraphNodeRef(node_id=self.rendition),
|
|
255
325
|
content_hash=self.audio_hash,
|
|
256
326
|
slice=TimeSlice(start=self.chunk_start, end=self.chunk_end)).to_dict()]
|
|
257
327
|
sources.extend(ts.to_source_ref() for ts in self.text_slices)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cjm-transcript-graph-schema
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.7
|
|
4
4
|
Summary: Audio-transcript layer schema for context graphs: Source, AudioSegment, Transcript, and Segment nodes with deterministic identity tuples and graph-node mapping, shared by the transcription, decomposition, and correction workflow cores.
|
|
5
5
|
Author-email: "Christian J. Mills" <9126128+cj-mills@users.noreply.github.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -12,8 +12,8 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
12
12
|
Requires-Python: >=3.12
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
License-File: LICENSE
|
|
15
|
-
Requires-Dist: cjm-context-graph-layer>=0.0.
|
|
16
|
-
Requires-Dist: cjm-context-graph-primitives>=0.0.
|
|
15
|
+
Requires-Dist: cjm-context-graph-layer>=0.0.7
|
|
16
|
+
Requires-Dist: cjm-context-graph-primitives>=0.0.12
|
|
17
17
|
Dynamic: license-file
|
|
18
18
|
|
|
19
19
|
# cjm-transcript-graph-schema
|
|
@@ -30,7 +30,7 @@ pip install cjm_transcript_graph_schema
|
|
|
30
30
|
## Project Structure
|
|
31
31
|
|
|
32
32
|
nbs/
|
|
33
|
-
└── schema.ipynb # The audio-transcript layer schema (where-graph-begins locked layer schema): `Source -> AudioSegment(coarse
|
|
33
|
+
└── schema.ipynb # The audio-transcript layer schema (where-graph-begins locked layer schema): `Source -> AudioSegment(coarse boundary) -> AudioRendition(model-input: raw | vocals | …) -> Transcript(per-transcriber variants)` emitted by transcription, extended with the fine `Segment` spine (per-rendition) by decomposition. Deterministic identity tuples per the stage-5 ratified rule; the `AudioRendition` node lets raw + preprocessed model-inputs of one boundary coexist in one graph. `Document` from the pre-CR-18 era dissolves into `Source`.
|
|
34
34
|
|
|
35
35
|
Total: 1 notebook
|
|
36
36
|
|
|
@@ -55,10 +55,12 @@ Detailed documentation for each module in the project:
|
|
|
55
55
|
|
|
56
56
|
> The audio-transcript layer schema (where-graph-begins locked layer
|
|
57
57
|
> schema):
|
|
58
|
-
> `Source -> AudioSegment(coarse
|
|
59
|
-
> emitted by transcription, extended with the fine `Segment` spine
|
|
60
|
-
> decomposition. Deterministic identity tuples per
|
|
61
|
-
> rule; `
|
|
58
|
+
> `Source -> AudioSegment(coarse boundary) -> AudioRendition(model-input: raw | vocals | …) -> Transcript(per-transcriber variants)`
|
|
59
|
+
> emitted by transcription, extended with the fine `Segment` spine
|
|
60
|
+
> (per-rendition) by decomposition. Deterministic identity tuples per
|
|
61
|
+
> the stage-5 ratified rule; the `AudioRendition` node lets raw +
|
|
62
|
+
> preprocessed model-inputs of one boundary coexist in one graph.
|
|
63
|
+
> `Document` from the pre-CR-18 era dissolves into `Source`.
|
|
62
64
|
|
|
63
65
|
#### Import
|
|
64
66
|
|
|
@@ -67,10 +69,12 @@ from cjm_transcript_graph_schema.schema import (
|
|
|
67
69
|
TranscriptGraphLabels,
|
|
68
70
|
source_node_id,
|
|
69
71
|
audio_segment_node_id,
|
|
72
|
+
audio_rendition_node_id,
|
|
70
73
|
transcript_node_id,
|
|
71
74
|
segment_node_id,
|
|
72
75
|
SourceNode,
|
|
73
76
|
AudioSegmentNode,
|
|
77
|
+
AudioRenditionNode,
|
|
74
78
|
TranscriptNode,
|
|
75
79
|
TranscriptSliceRef,
|
|
76
80
|
SegmentNode
|
|
@@ -95,36 +99,60 @@ def audio_segment_node_id(
|
|
|
95
99
|
"""
|
|
96
100
|
AudioSegment identity = (source, boundary range).
|
|
97
101
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
re-derivation reproduces
|
|
102
|
+
Rendition-independent by design: an AudioSegment is a BOUNDARY of the source
|
|
103
|
+
(an audio fact), shared across every model-input rendition of it. The
|
|
104
|
+
boundary computation is pure and deterministic, so re-derivation reproduces
|
|
105
|
+
the id; the model-input WAV lives on the `AudioRendition` child, not here.
|
|
101
106
|
"""
|
|
102
107
|
```
|
|
103
108
|
|
|
104
109
|
``` python
|
|
105
|
-
def
|
|
110
|
+
def audio_rendition_node_id(
|
|
106
111
|
audio_segment_id: str, # Owning AudioSegment node id
|
|
107
|
-
|
|
108
|
-
|
|
112
|
+
chain: List[str], # Ordered preprocessing-chain descriptors; [] = the raw convert-only rendition
|
|
113
|
+
) -> str: # Deterministic AudioRendition node id
|
|
114
|
+
"""
|
|
115
|
+
AudioRendition identity = (audio segment, preprocessing chain).
|
|
116
|
+
|
|
117
|
+
A rendition is one model-input audio OF an AudioSegment. The chain is the
|
|
118
|
+
ordered list of preprocessing steps that produced it (each an opaque
|
|
119
|
+
descriptor string, e.g. ``"source_separation:cjm-media-plugin-demucs@<cfg>"``);
|
|
120
|
+
an EMPTY chain is the raw convert-only rendition, whose id is therefore
|
|
121
|
+
stable and chain-free. The universal 16k-mono resample is implicit (not a
|
|
122
|
+
chain step) — so raw + vocals are distinct renditions under one shared
|
|
123
|
+
AudioSegment, and the layer's identity-mismatch check never collides them.
|
|
124
|
+
Re-derivable from the manifest chain alone (extenders recompute, never
|
|
125
|
+
search).
|
|
126
|
+
"""
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
``` python
|
|
130
|
+
def transcript_node_id(
|
|
131
|
+
rendition_id: str, # Owning AudioRendition node id
|
|
132
|
+
transcriber: str, # Transcriber capability name (e.g. "cjm-transcription-plugin-whisper")
|
|
133
|
+
config_hash: str, # Transcriber config hash
|
|
109
134
|
) -> str: # Deterministic Transcript node id
|
|
110
135
|
"""
|
|
111
|
-
Transcript identity = (audio
|
|
136
|
+
Transcript identity = (audio rendition, transcriber, config) — MIRRORS the
|
|
112
137
|
capability cache key UNIQUE(audio_path, config_hash), so the graph node is
|
|
113
|
-
the durable face of the cached row (the structural E13 fix).
|
|
138
|
+
the durable face of the cached row (the structural E13 fix). Keyed on the
|
|
139
|
+
RENDITION, so a raw vs vocals transcript of the same boundary are distinct.
|
|
114
140
|
"""
|
|
115
141
|
```
|
|
116
142
|
|
|
117
143
|
``` python
|
|
118
144
|
def segment_node_id(
|
|
119
|
-
|
|
120
|
-
vad_config_hash: str,
|
|
121
|
-
chunk_start: float,
|
|
122
|
-
chunk_end: float,
|
|
145
|
+
rendition_id: str, # Owning AudioRendition node id
|
|
146
|
+
vad_config_hash: str, # VAD capability config hash (skeleton identity input)
|
|
147
|
+
chunk_start: float, # VAD chunk start (chunk-local seconds within the rendition WAV)
|
|
148
|
+
chunk_end: float, # VAD chunk end (chunk-local seconds)
|
|
123
149
|
) -> str: # Deterministic Segment node id
|
|
124
150
|
"""
|
|
125
|
-
Fine Segment identity = audio-side only (audio
|
|
151
|
+
Fine Segment identity = audio-side only (audio rendition, VAD config, chunk
|
|
126
152
|
range) — so the skeleton's ids are SHARED across transcribers by
|
|
127
|
-
construction (C4 "store agreement once" falls out of identity design).
|
|
153
|
+
construction (C4 "store agreement once" falls out of identity design). Keyed
|
|
154
|
+
on the RENDITION: each rendition has its own fine spine (vocals isolation can
|
|
155
|
+
yield different VAD chunk boundaries than raw).
|
|
128
156
|
"""
|
|
129
157
|
```
|
|
130
158
|
|
|
@@ -166,23 +194,23 @@ class SourceNode:
|
|
|
166
194
|
@dataclass
|
|
167
195
|
class AudioSegmentNode:
|
|
168
196
|
"""
|
|
169
|
-
Coarse ~5-min spine member
|
|
170
|
-
|
|
197
|
+
Coarse ~5-min spine member: a BOUNDARY range of the Source (an audio fact),
|
|
198
|
+
shared across every model-input rendition of it.
|
|
171
199
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
200
|
+
Hashless by design: a boundary is not a materialized artifact — a hash over
|
|
201
|
+
the sliced ORIGINAL source bytes is not materializable without extracting
|
|
202
|
+
per-range artifacts (decoded ranges are not byte ranges). The materialized
|
|
203
|
+
model-input WAV lives on the `AudioRendition` child (which carries its
|
|
204
|
+
content hash); the structural chain to the Source rides the PART_OF edge +
|
|
205
|
+
the start/end properties. `segment_path` (the source-codec cut) stays here as
|
|
206
|
+
a rendition-independent archival pointer.
|
|
177
207
|
"""
|
|
178
208
|
|
|
179
209
|
source: str # Owning Source node id
|
|
180
210
|
index: int # Position in the coarse spine (0-based)
|
|
181
211
|
start: float # Boundary start (source-coordinate seconds)
|
|
182
212
|
end: float # Boundary end (source-coordinate seconds)
|
|
183
|
-
|
|
184
|
-
model_input_hash: str # Content hash over that WAV
|
|
185
|
-
segment_path: Optional[str] # Source-codec cut file (archival pointer)
|
|
213
|
+
segment_path: Optional[str] # Source-codec cut file (rendition-independent archival pointer)
|
|
186
214
|
|
|
187
215
|
def id(self) -> str: # Deterministic node id
|
|
188
216
|
"""Deterministic node id."""
|
|
@@ -192,30 +220,69 @@ class AudioSegmentNode:
|
|
|
192
220
|
"Deterministic node id."
|
|
193
221
|
|
|
194
222
|
def to_graph_node(self) -> Dict[str, Any]: # Node wire dict
|
|
195
|
-
"""Build the AudioSegment node wire dict."""
|
|
223
|
+
"""Build the AudioSegment node wire dict (hashless boundary; sources empty)."""
|
|
196
224
|
props: Dict[str, Any] = {
|
|
197
|
-
"Build the AudioSegment node wire dict."
|
|
225
|
+
"Build the AudioSegment node wire dict (hashless boundary; sources empty)."
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
``` python
|
|
229
|
+
@dataclass
|
|
230
|
+
class AudioRenditionNode:
|
|
231
|
+
"""
|
|
232
|
+
A model-input rendition OF an AudioSegment — the materialized 16k-mono WAV
|
|
233
|
+
consumed by transcription/VAD/FA. OWNS the model-input (E14: the audio of
|
|
234
|
+
record); the AudioSegment above it is a hashless boundary.
|
|
235
|
+
|
|
236
|
+
Identity = (audio segment, preprocessing chain). The raw convert-only path is
|
|
237
|
+
an empty chain (its id is stable + chain-free); vocals isolation / future
|
|
238
|
+
speech-enhancement are non-empty chains. Raw + vocals are therefore distinct
|
|
239
|
+
renditions that COEXIST under one AudioSegment — the divergent model-input
|
|
240
|
+
hash that used to collide the AudioSegment now lives on distinct rendition
|
|
241
|
+
nodes. Connects UP to its AudioSegment by DERIVED_FROM (it is derived from the
|
|
242
|
+
segment's audio); the production act is recorded separately by a CR-18
|
|
243
|
+
`Derivation` event.
|
|
244
|
+
"""
|
|
245
|
+
|
|
246
|
+
audio_segment: str # Owning AudioSegment node id
|
|
247
|
+
model_input_path: str # The 16kHz mono WAV consumed by transcription/VAD/FA
|
|
248
|
+
model_input_hash: str # Content hash over that WAV (the rendition's identity-of-record artifact)
|
|
249
|
+
chain: List[str] = field(...) # Ordered preprocessing-chain descriptors; [] = raw convert-only
|
|
250
|
+
|
|
251
|
+
def id(self) -> str: # Deterministic node id
|
|
252
|
+
"""Deterministic node id."""
|
|
253
|
+
return audio_rendition_node_id(self.audio_segment, self.chain)
|
|
254
|
+
|
|
255
|
+
def to_graph_node(self) -> Dict[str, Any]: # Node wire dict
|
|
256
|
+
"Deterministic node id."
|
|
257
|
+
|
|
258
|
+
def to_graph_node(self) -> Dict[str, Any]: # Node wire dict
|
|
259
|
+
"""Build the AudioRendition node wire dict (owns the model-input SourceRef)."""
|
|
260
|
+
props: Dict[str, Any] = {
|
|
261
|
+
"Build the AudioRendition node wire dict (owns the model-input SourceRef)."
|
|
262
|
+
|
|
263
|
+
def derived_edge(self) -> Dict[str, Any]: # Edge wire dict
|
|
264
|
+
"DERIVED_FROM edge: this rendition derives from its AudioSegment's audio."
|
|
198
265
|
```
|
|
199
266
|
|
|
200
267
|
``` python
|
|
201
268
|
@dataclass
|
|
202
269
|
class TranscriptNode:
|
|
203
270
|
"""
|
|
204
|
-
One transcriber's text for one
|
|
271
|
+
One transcriber's text for one AudioRendition (per-transcriber variants at
|
|
205
272
|
the coarse layer — cross-transcriber divergence lives here, C4/C14).
|
|
206
273
|
"""
|
|
207
274
|
|
|
208
|
-
|
|
275
|
+
rendition: str # Owning AudioRendition node id
|
|
209
276
|
transcriber: str # Transcriber capability name
|
|
210
277
|
config_hash: str # Transcriber config hash
|
|
211
278
|
text: str # The transcript text (stored ONCE here; fine Segments slice into it)
|
|
212
|
-
audio_hash: str # Content hash of the consumed model-input WAV
|
|
279
|
+
audio_hash: str # Content hash of the consumed model-input WAV (the rendition's)
|
|
213
280
|
metadata: Dict[str, Any] = field(...) # Transcriber-reported metadata
|
|
214
281
|
asserted_at: Optional[float] # Derivation timestamp; None = now
|
|
215
282
|
|
|
216
283
|
def id(self) -> str: # Deterministic node id
|
|
217
284
|
"""Deterministic node id."""
|
|
218
|
-
return transcript_node_id(self.
|
|
285
|
+
return transcript_node_id(self.rendition, self.transcriber, self.config_hash)
|
|
219
286
|
|
|
220
287
|
def to_graph_node(self) -> Dict[str, Any]: # Node wire dict
|
|
221
288
|
"Deterministic node id."
|
|
@@ -226,7 +293,7 @@ class TranscriptNode:
|
|
|
226
293
|
"Build the Transcript node wire dict (capability attribution included)."
|
|
227
294
|
|
|
228
295
|
def derived_edge(self) -> Dict[str, Any]: # Edge wire dict
|
|
229
|
-
"DERIVED_FROM edge: this Transcript derives from its
|
|
296
|
+
"DERIVED_FROM edge: this Transcript derives from its AudioRendition."
|
|
230
297
|
```
|
|
231
298
|
|
|
232
299
|
``` python
|
|
@@ -252,7 +319,8 @@ class TranscriptSliceRef:
|
|
|
252
319
|
class SegmentNode:
|
|
253
320
|
"""
|
|
254
321
|
Fine spine member: one VAD chunk — IMMUTABLE audio range + CORRECTABLE
|
|
255
|
-
text (the immutable-audio/mutable-text spine seam).
|
|
322
|
+
text (the immutable-audio/mutable-text spine seam). PART_OF its AudioRendition
|
|
323
|
+
(each rendition has its own fine spine).
|
|
256
324
|
|
|
257
325
|
Layer-0 `text` is the ACCURACY transcriber's alignment; the designation is
|
|
258
326
|
per-segment provenance, not global config (`text_from` names the
|
|
@@ -260,26 +328,26 @@ class SegmentNode:
|
|
|
260
328
|
`text_slices`, the authoritative one included).
|
|
261
329
|
"""
|
|
262
330
|
|
|
263
|
-
|
|
331
|
+
rendition: str # Owning AudioRendition node id
|
|
264
332
|
vad_config_hash: str # VAD config hash (skeleton identity input)
|
|
265
|
-
chunk_start: float # VAD chunk start (chunk-local seconds within the
|
|
333
|
+
chunk_start: float # VAD chunk start (chunk-local seconds within the rendition WAV)
|
|
266
334
|
chunk_end: float # VAD chunk end (chunk-local seconds)
|
|
267
335
|
index: int # Source-wide fine-spine index (0-based)
|
|
268
336
|
start_time: float # Source-coordinate start (navigation)
|
|
269
337
|
end_time: float # Source-coordinate end
|
|
270
338
|
text: str = '' # Layer-0 text (accuracy alignment; "" = no aligned words, D14 class)
|
|
271
|
-
audio_hash: str = '' # Content hash of the owning
|
|
339
|
+
audio_hash: str = '' # Content hash of the owning rendition's model-input WAV
|
|
272
340
|
source: Optional[str] # Source node id (convenience for direct filters)
|
|
273
341
|
text_from: Optional[str] # Authoritative Transcript node id (None when text is empty)
|
|
274
342
|
text_slices: List[TranscriptSliceRef] = field(...) # All per-transcriber slice refs
|
|
275
343
|
|
|
276
344
|
def id(self) -> str: # Deterministic node id
|
|
277
|
-
"""Deterministic node id (audio-side identity; shared across transcribers)."""
|
|
278
|
-
return segment_node_id(self.
|
|
345
|
+
"""Deterministic node id (audio-side identity; shared across transcribers, per-rendition)."""
|
|
346
|
+
return segment_node_id(self.rendition, self.vad_config_hash,
|
|
279
347
|
self.chunk_start, self.chunk_end)
|
|
280
348
|
|
|
281
349
|
def to_graph_node(self) -> Dict[str, Any]: # Node wire dict
|
|
282
|
-
"Deterministic node id (audio-side identity; shared across transcribers)."
|
|
350
|
+
"Deterministic node id (audio-side identity; shared across transcribers, per-rendition)."
|
|
283
351
|
|
|
284
352
|
def to_graph_node(self) -> Dict[str, Any]: # Node wire dict
|
|
285
353
|
"""Build the Segment node wire dict (audio ref + per-transcriber text slice refs)."""
|
|
@@ -15,7 +15,7 @@ classifiers = [
|
|
|
15
15
|
"Programming Language :: Python :: 3",
|
|
16
16
|
"Programming Language :: Python :: 3 :: Only",
|
|
17
17
|
]
|
|
18
|
-
dependencies = ['cjm-context-graph-layer>=0.0.
|
|
18
|
+
dependencies = ['cjm-context-graph-layer>=0.0.7', 'cjm-context-graph-primitives>=0.0.12']
|
|
19
19
|
|
|
20
20
|
[project.urls]
|
|
21
21
|
Repository = "https://github.com/cj-mills/cjm-transcript-graph-schema"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.0.5"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|