cjm-transcript-graph-schema 0.0.8__tar.gz → 0.0.12__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.8/cjm_transcript_graph_schema.egg-info → cjm_transcript_graph_schema-0.0.12}/PKG-INFO +7 -3
- {cjm_transcript_graph_schema-0.0.8 → cjm_transcript_graph_schema-0.0.12}/README.md +6 -2
- cjm_transcript_graph_schema-0.0.12/cjm_transcript_graph_schema/__init__.py +1 -0
- {cjm_transcript_graph_schema-0.0.8 → cjm_transcript_graph_schema-0.0.12}/cjm_transcript_graph_schema/schema.py +146 -9
- {cjm_transcript_graph_schema-0.0.8 → cjm_transcript_graph_schema-0.0.12/cjm_transcript_graph_schema.egg-info}/PKG-INFO +7 -3
- {cjm_transcript_graph_schema-0.0.8 → cjm_transcript_graph_schema-0.0.12}/tests/test_schema.py +122 -8
- cjm_transcript_graph_schema-0.0.8/cjm_transcript_graph_schema/__init__.py +0 -1
- {cjm_transcript_graph_schema-0.0.8 → cjm_transcript_graph_schema-0.0.12}/LICENSE +0 -0
- {cjm_transcript_graph_schema-0.0.8 → cjm_transcript_graph_schema-0.0.12}/MANIFEST.in +0 -0
- {cjm_transcript_graph_schema-0.0.8 → cjm_transcript_graph_schema-0.0.12}/cjm_transcript_graph_schema.egg-info/SOURCES.txt +0 -0
- {cjm_transcript_graph_schema-0.0.8 → cjm_transcript_graph_schema-0.0.12}/cjm_transcript_graph_schema.egg-info/dependency_links.txt +0 -0
- {cjm_transcript_graph_schema-0.0.8 → cjm_transcript_graph_schema-0.0.12}/cjm_transcript_graph_schema.egg-info/requires.txt +0 -0
- {cjm_transcript_graph_schema-0.0.8 → cjm_transcript_graph_schema-0.0.12}/cjm_transcript_graph_schema.egg-info/top_level.txt +0 -0
- {cjm_transcript_graph_schema-0.0.8 → cjm_transcript_graph_schema-0.0.12}/pyproject.toml +0 -0
- {cjm_transcript_graph_schema-0.0.8 → cjm_transcript_graph_schema-0.0.12}/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.12
|
|
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
|
|
@@ -23,7 +23,8 @@ Audio-transcript layer schema for context graphs: Source, AudioSegment, Transcri
|
|
|
23
23
|
|
|
24
24
|
## Modules
|
|
25
25
|
|
|
26
|
-
- **`cjm_transcript_graph_schema.
|
|
26
|
+
- **`cjm_transcript_graph_schema.__init__`**
|
|
27
|
+
- **`cjm_transcript_graph_schema.schema`** — 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, and with the intrinsic Collection layer ABOVE Source (book/series/course; proposed-by-tool / confirmed-by-actor, order optional) captured at transcription hand-off. 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.
|
|
27
28
|
|
|
28
29
|
## API
|
|
29
30
|
|
|
@@ -32,6 +33,7 @@ Audio-transcript layer schema for context graphs: Source, AudioSegment, Transcri
|
|
|
32
33
|
- `AudioRenditionNode` _class_ — A model-input rendition OF an AudioSegment — the materialized 16k-mono WAV
|
|
33
34
|
- `AudioSegmentNode` _class_ — Coarse ~5-min spine member: a BOUNDARY range of the Source (an audio fact),
|
|
34
35
|
- `CollectionNode` _class_ — An intrinsic collection (book, podcast series, lecture course) — the
|
|
36
|
+
- `RespinedChunkEntry` _class_ — One chunk respine as the Source records it (0b4d5cfa (4)): the entry the
|
|
35
37
|
- `SegmentNode` _class_ — Fine spine member: one VAD chunk — IMMUTABLE audio range + CORRECTABLE
|
|
36
38
|
- `SourceNode` _class_ — The provenance root: one ingested media file.
|
|
37
39
|
- `TranscriptGraphLabels` _class_ — Node labels of the audio-transcript layer schema.
|
|
@@ -41,6 +43,8 @@ Audio-transcript layer schema for context graphs: Source, AudioSegment, Transcri
|
|
|
41
43
|
- `audio_segment_node_id` _function_ — AudioSegment identity = (source, boundary range).
|
|
42
44
|
- `collection_edges` _function_ — Membership edges for a collection (ae3464fc: ORDER IS OPTIONAL).
|
|
43
45
|
- `collection_node_id` _function_ — Collection identity = the NORMALIZED TITLE (ratified fork, hub-session
|
|
46
|
+
- `external_config_hash` _function_ — The config hash an external landing carries where a capability's effective
|
|
47
|
+
- `external_transcriber_name` _function_ — Transcriber name for an EXTERNAL landing (ruling 9ffce5f7 (1)): the model id
|
|
44
48
|
- `normalize_collection_title` _function_ — Normalize a collection title for identity derivation.
|
|
45
49
|
- `segment_node_id` _function_ — Fine Segment identity = audio-side only (audio rendition, VAD config, chunk
|
|
46
50
|
- `source_node_id` _function_ — Source identity = the ingested file's content hash (Thread-1 ingested-root identity).
|
|
@@ -49,4 +53,4 @@ Audio-transcript layer schema for context graphs: Source, AudioSegment, Transcri
|
|
|
49
53
|
## Dependencies
|
|
50
54
|
|
|
51
55
|
**Depends on:** `cjm-context-graph-layer`, `cjm-context-graph-primitives`
|
|
52
|
-
**Used by:** `cjm-context-graph-layer`, `cjm-transcript-correction-core`, `cjm-transcript-
|
|
56
|
+
**Used by:** `cjm-context-graph-layer`, `cjm-transcript-correction-core`, `cjm-transcript-decomp-core`, `cjm-transcription-core`, `cjm-transcription-qt`, `cjm-workflow-hub-qt`
|
|
@@ -6,7 +6,8 @@ Audio-transcript layer schema for context graphs: Source, AudioSegment, Transcri
|
|
|
6
6
|
|
|
7
7
|
## Modules
|
|
8
8
|
|
|
9
|
-
- **`cjm_transcript_graph_schema.
|
|
9
|
+
- **`cjm_transcript_graph_schema.__init__`**
|
|
10
|
+
- **`cjm_transcript_graph_schema.schema`** — 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, and with the intrinsic Collection layer ABOVE Source (book/series/course; proposed-by-tool / confirmed-by-actor, order optional) captured at transcription hand-off. 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.
|
|
10
11
|
|
|
11
12
|
## API
|
|
12
13
|
|
|
@@ -15,6 +16,7 @@ Audio-transcript layer schema for context graphs: Source, AudioSegment, Transcri
|
|
|
15
16
|
- `AudioRenditionNode` _class_ — A model-input rendition OF an AudioSegment — the materialized 16k-mono WAV
|
|
16
17
|
- `AudioSegmentNode` _class_ — Coarse ~5-min spine member: a BOUNDARY range of the Source (an audio fact),
|
|
17
18
|
- `CollectionNode` _class_ — An intrinsic collection (book, podcast series, lecture course) — the
|
|
19
|
+
- `RespinedChunkEntry` _class_ — One chunk respine as the Source records it (0b4d5cfa (4)): the entry the
|
|
18
20
|
- `SegmentNode` _class_ — Fine spine member: one VAD chunk — IMMUTABLE audio range + CORRECTABLE
|
|
19
21
|
- `SourceNode` _class_ — The provenance root: one ingested media file.
|
|
20
22
|
- `TranscriptGraphLabels` _class_ — Node labels of the audio-transcript layer schema.
|
|
@@ -24,6 +26,8 @@ Audio-transcript layer schema for context graphs: Source, AudioSegment, Transcri
|
|
|
24
26
|
- `audio_segment_node_id` _function_ — AudioSegment identity = (source, boundary range).
|
|
25
27
|
- `collection_edges` _function_ — Membership edges for a collection (ae3464fc: ORDER IS OPTIONAL).
|
|
26
28
|
- `collection_node_id` _function_ — Collection identity = the NORMALIZED TITLE (ratified fork, hub-session
|
|
29
|
+
- `external_config_hash` _function_ — The config hash an external landing carries where a capability's effective
|
|
30
|
+
- `external_transcriber_name` _function_ — Transcriber name for an EXTERNAL landing (ruling 9ffce5f7 (1)): the model id
|
|
27
31
|
- `normalize_collection_title` _function_ — Normalize a collection title for identity derivation.
|
|
28
32
|
- `segment_node_id` _function_ — Fine Segment identity = audio-side only (audio rendition, VAD config, chunk
|
|
29
33
|
- `source_node_id` _function_ — Source identity = the ingested file's content hash (Thread-1 ingested-root identity).
|
|
@@ -32,4 +36,4 @@ Audio-transcript layer schema for context graphs: Source, AudioSegment, Transcri
|
|
|
32
36
|
## Dependencies
|
|
33
37
|
|
|
34
38
|
**Depends on:** `cjm-context-graph-layer`, `cjm-context-graph-primitives`
|
|
35
|
-
**Used by:** `cjm-context-graph-layer`, `cjm-transcript-correction-core`, `cjm-transcript-
|
|
39
|
+
**Used by:** `cjm-context-graph-layer`, `cjm-transcript-correction-core`, `cjm-transcript-decomp-core`, `cjm-transcription-core`, `cjm-transcription-qt`, `cjm-workflow-hub-qt`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.12"
|
|
@@ -14,17 +14,20 @@ from cjm_context_graph_primitives.slices import CharSlice, FullContent, TimeSlic
|
|
|
14
14
|
|
|
15
15
|
class TranscriptGraphLabels:
|
|
16
16
|
"""Node labels of the audio-transcript layer schema."""
|
|
17
|
+
|
|
17
18
|
SOURCE = "Source" # Provenance root: one ingested media file
|
|
18
19
|
AUDIO_SEGMENT = "AudioSegment" # Coarse ~5-min spine member: a boundary range of the Source (an audio FACT; rendition-independent)
|
|
19
20
|
AUDIO_RENDITION = "AudioRendition" # A model-input rendition OF an AudioSegment (raw convert | vocals | future enhancement); OWNS the model-input WAV
|
|
20
21
|
TRANSCRIPT = "Transcript" # Per-transcriber text variant of one AudioRendition
|
|
21
22
|
SEGMENT = "Segment" # Fine spine member: one VAD chunk (immutable audio + correctable text), per-rendition
|
|
22
23
|
COLLECTION = "Collection" # Intrinsic collection (book / series / course): the fractal spine layer ABOVE Source
|
|
24
|
+
REFERENCE = "Reference" # Human-added resource link attached to a Source (publisher page / author post / related notes / cited work): Source -HAS_REFERENCE-> Reference (ruling a7ca900d (3), item ae103970)
|
|
23
25
|
|
|
24
26
|
@classmethod
|
|
25
27
|
def all(cls) -> list: # All schema labels
|
|
26
28
|
"""All schema labels."""
|
|
27
|
-
return [cls.SOURCE, cls.AUDIO_SEGMENT, cls.AUDIO_RENDITION, cls.TRANSCRIPT, cls.SEGMENT, cls.COLLECTION
|
|
29
|
+
return [cls.SOURCE, cls.AUDIO_SEGMENT, cls.AUDIO_RENDITION, cls.TRANSCRIPT, cls.SEGMENT, cls.COLLECTION,
|
|
30
|
+
cls.REFERENCE]
|
|
28
31
|
|
|
29
32
|
|
|
30
33
|
def source_node_id(
|
|
@@ -78,23 +81,68 @@ def transcript_node_id(
|
|
|
78
81
|
return derive_node_id("transcript", rendition_id, transcriber, config_hash)
|
|
79
82
|
|
|
80
83
|
|
|
84
|
+
EXTERNAL_TRANSCRIBER_MARKER = "/manual" # Suffix naming an operator-landed (pasted) transcript producer
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def external_transcriber_name(
|
|
88
|
+
model_id: str, # The external model that produced the text (e.g. "gemini-2.5-pro")
|
|
89
|
+
) -> str: # The transcriber name an external landing files under
|
|
90
|
+
"""Transcriber name for an EXTERNAL landing (ruling 9ffce5f7 (1)): the model id
|
|
91
|
+
plus a manual marker, so a pasted transcript is a THIRD TRANSCRIBER beside the
|
|
92
|
+
local capabilities — decomp folds it per chunk like any other variant, and the
|
|
93
|
+
marker keeps it distinguishable from a capability run of the same model."""
|
|
94
|
+
mid = (model_id or "").strip()
|
|
95
|
+
if not mid:
|
|
96
|
+
raise ValueError("external_transcriber_name: model_id is required")
|
|
97
|
+
return mid + EXTERNAL_TRANSCRIBER_MARKER
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def external_config_hash(
|
|
101
|
+
model_id: str, # The external model id
|
|
102
|
+
prompt_hash: str, # Hash of the prompt template the operator used ("" = no prompt recorded)
|
|
103
|
+
) -> str: # "sha256:<hex>" — the config-hash slot of an external landing
|
|
104
|
+
"""The config hash an external landing carries where a capability's effective
|
|
105
|
+
config hash goes: sha256 over (model id, prompt hash). The prompt is DATA
|
|
106
|
+
(f304d31d), so a different prompt template is a different variant — and the
|
|
107
|
+
Transcript identity (rendition, transcriber, config_hash) stays recomputable
|
|
108
|
+
from the derived manifest alone."""
|
|
109
|
+
import hashlib
|
|
110
|
+
h = hashlib.sha256()
|
|
111
|
+
h.update((model_id or "").strip().encode("utf-8"))
|
|
112
|
+
h.update(b"\n")
|
|
113
|
+
h.update((prompt_hash or "").strip().encode("utf-8"))
|
|
114
|
+
return "sha256:" + h.hexdigest()
|
|
115
|
+
|
|
116
|
+
|
|
81
117
|
def segment_node_id(
|
|
82
118
|
rendition_id: str, # Owning AudioRendition node id
|
|
83
|
-
vad_config_hash: str, #
|
|
119
|
+
vad_config_hash: str, # Skeleton config hash (identity input: VAD config, + split policy when a split stage ran)
|
|
84
120
|
chunk_start: float, # VAD chunk start (chunk-local seconds within the rendition WAV)
|
|
85
121
|
chunk_end: float, # VAD chunk end (chunk-local seconds)
|
|
122
|
+
identity_salt: Optional[str] = None, # Respine salt (0b4d5cfa (3)): joins the id ONLY; None = the spine's original derivation
|
|
86
123
|
) -> str: # Deterministic Segment node id
|
|
87
124
|
"""Fine Segment identity = audio-side only (audio rendition, VAD config, chunk
|
|
88
125
|
range) — so the skeleton's ids are SHARED across transcribers by
|
|
89
126
|
construction (C4 "store agreement once" falls out of identity design). Keyed
|
|
90
127
|
on the RENDITION: each rendition has its own fine spine (vocals isolation can
|
|
91
|
-
yield different VAD chunk boundaries than raw).
|
|
128
|
+
yield different VAD chunk boundaries than raw).
|
|
129
|
+
|
|
130
|
+
A CHUNK RESPINE (0b4d5cfa) re-derives one coarse chunk into the LIVE spine
|
|
131
|
+
from a landed external transcript: its segments keep the live skeleton hash
|
|
132
|
+
(every picker groups spines by it) and fork identity through `identity_salt`
|
|
133
|
+
= the landed Transcript's node id, so the same landing twice collides into
|
|
134
|
+
a no-op and a different landing mints fresh segments. None (the default)
|
|
135
|
+
reproduces every pre-salt id unchanged."""
|
|
136
|
+
if identity_salt:
|
|
137
|
+
return derive_node_id("segment", rendition_id, vad_config_hash, chunk_start, chunk_end,
|
|
138
|
+
identity_salt)
|
|
92
139
|
return derive_node_id("segment", rendition_id, vad_config_hash, chunk_start, chunk_end)
|
|
93
140
|
|
|
94
141
|
|
|
95
142
|
@dataclass
|
|
96
143
|
class SourceNode:
|
|
97
144
|
"""The provenance root: one ingested media file."""
|
|
145
|
+
|
|
98
146
|
content_hash: str # Content hash over the file ("algo:hexdigest"; the identity input)
|
|
99
147
|
path: str # Original media path (location, may dangle; identity is the hash)
|
|
100
148
|
title: Optional[str] = None # Display title; None = path stem
|
|
@@ -133,6 +181,7 @@ class AudioSegmentNode:
|
|
|
133
181
|
content hash); the structural chain to the Source rides the PART_OF edge +
|
|
134
182
|
the start/end properties. `segment_path` (the source-codec cut) stays here as
|
|
135
183
|
a rendition-independent archival pointer."""
|
|
184
|
+
|
|
136
185
|
source: str # Owning Source node id
|
|
137
186
|
index: int # Position in the coarse spine (0-based)
|
|
138
187
|
start: float # Boundary start (source-coordinate seconds)
|
|
@@ -176,6 +225,7 @@ class AudioRenditionNode:
|
|
|
176
225
|
nodes. Connects UP to its AudioSegment by DERIVED_FROM (it is derived from the
|
|
177
226
|
segment's audio); the production act is recorded separately by a CR-18
|
|
178
227
|
`Derivation` event."""
|
|
228
|
+
|
|
179
229
|
audio_segment: str # Owning AudioSegment node id
|
|
180
230
|
model_input_path: str # The 16kHz mono WAV consumed by transcription/VAD/FA
|
|
181
231
|
model_input_hash: str # Content hash over that WAV (the rendition's identity-of-record artifact)
|
|
@@ -213,6 +263,7 @@ class AudioRenditionNode:
|
|
|
213
263
|
class TranscriptNode:
|
|
214
264
|
"""One transcriber's text for one AudioRendition (per-transcriber variants at
|
|
215
265
|
the coarse layer — cross-transcriber divergence lives here, C4/C14)."""
|
|
266
|
+
|
|
216
267
|
rendition: str # Owning AudioRendition node id
|
|
217
268
|
transcriber: str # Transcriber capability name
|
|
218
269
|
config_hash: str # Transcriber config hash
|
|
@@ -220,6 +271,8 @@ class TranscriptNode:
|
|
|
220
271
|
audio_hash: str # Content hash of the consumed model-input WAV (the rendition's)
|
|
221
272
|
metadata: Dict[str, Any] = field(default_factory=dict) # Transcriber-reported metadata
|
|
222
273
|
asserted_at: Optional[float] = None # Derivation timestamp; None = now
|
|
274
|
+
actor: Optional[str] = None # Attribution actor; None = "capability:<transcriber>" (an EXTERNAL landing names the operator, 9ffce5f7 (1))
|
|
275
|
+
method: str = "transcribe" # Attribution method ("transcribe" for a capability run; "external-landing" for pasted text)
|
|
223
276
|
|
|
224
277
|
@property
|
|
225
278
|
def id(self) -> str: # Deterministic node id
|
|
@@ -236,7 +289,7 @@ class TranscriptNode:
|
|
|
236
289
|
}
|
|
237
290
|
if self.metadata:
|
|
238
291
|
props["metadata"] = dict(self.metadata)
|
|
239
|
-
props.update(attribution(f"capability:{self.transcriber}", method=
|
|
292
|
+
props.update(attribution(self.actor or f"capability:{self.transcriber}", method=self.method,
|
|
240
293
|
asserted_at=self.asserted_at))
|
|
241
294
|
return {
|
|
242
295
|
"id": self.id,
|
|
@@ -251,12 +304,25 @@ class TranscriptNode:
|
|
|
251
304
|
"""DERIVED_FROM edge: this Transcript derives from its AudioRendition."""
|
|
252
305
|
return make_edge(self.id, self.rendition, OverlayRelations.DERIVED_FROM)
|
|
253
306
|
|
|
307
|
+
def supersedes_edge(
|
|
308
|
+
self,
|
|
309
|
+
prior_transcript_id: str, # The Transcript variant this one replaces (same rendition + transcriber, an older config)
|
|
310
|
+
) -> Dict[str, Any]: # Edge wire dict
|
|
311
|
+
"""SUPERSEDES edge: this variant replaces a prior one for the same (rendition,
|
|
312
|
+
transcriber) — a chunk-targeted re-run under a new config, or a re-landed
|
|
313
|
+
external transcript (ruling 910f3692 (2)). The prior node STAYS as append-only
|
|
314
|
+
history; readers treat a variant with an incoming SUPERSEDES edge as NOT LIVE."""
|
|
315
|
+
if prior_transcript_id == self.id:
|
|
316
|
+
raise ValueError("supersedes_edge: a Transcript cannot supersede itself")
|
|
317
|
+
return make_edge(self.id, prior_transcript_id, OverlayRelations.SUPERSEDES)
|
|
318
|
+
|
|
254
319
|
|
|
255
320
|
@dataclass
|
|
256
321
|
class TranscriptSliceRef:
|
|
257
322
|
"""One per-transcriber char-range reference for a fine Segment: where this
|
|
258
323
|
segment's text lives inside a Transcript node's text (Thread-1
|
|
259
324
|
slices-until-promoted — variants are slices, never duplicated fine text)."""
|
|
325
|
+
|
|
260
326
|
transcript: str # Transcript node id
|
|
261
327
|
start_char: int # Slice start into the Transcript's text
|
|
262
328
|
end_char: int # Slice end
|
|
@@ -273,14 +339,24 @@ class TranscriptSliceRef:
|
|
|
273
339
|
class SegmentNode:
|
|
274
340
|
"""Fine spine member: one VAD chunk — IMMUTABLE audio range + CORRECTABLE
|
|
275
341
|
text (the immutable-audio/mutable-text spine seam). PART_OF its AudioRendition
|
|
276
|
-
(each rendition has its own fine spine).
|
|
342
|
+
(each rendition has its own fine spine). Parallel SPINES coexist under one
|
|
343
|
+
rendition (sentence-split, DEC f1024568): the skeleton hash feeds every node
|
|
344
|
+
id AND rides the queryable `skeleton_hash` prop, so spine-scoped reads never
|
|
345
|
+
mix coexisting skeletons (pre-split nodes lack the prop = the legacy spine).
|
|
277
346
|
|
|
278
347
|
Layer-0 `text` is the ACCURACY transcriber's alignment; the designation is
|
|
279
348
|
per-segment provenance, not global config (`text_from` names the
|
|
280
349
|
authoritative Transcript; every transcriber's char range rides
|
|
281
|
-
`text_slices`, the authoritative one included).
|
|
350
|
+
`text_slices`, the authoritative one included).
|
|
351
|
+
|
|
352
|
+
A CHUNK RESPINE (0b4d5cfa) replaces one coarse chunk's segments INSIDE the
|
|
353
|
+
live spine: the new segments carry `identity_salt` (joins the id only —
|
|
354
|
+
`skeleton_hash` stays the live spine's, so no reader splits the spine),
|
|
355
|
+
the old ones are stamped `superseded_by` (the respine op id) and leave the
|
|
356
|
+
live view — readers filter `superseded_by is null`; nothing is deleted."""
|
|
357
|
+
|
|
282
358
|
rendition: str # Owning AudioRendition node id
|
|
283
|
-
vad_config_hash: str #
|
|
359
|
+
vad_config_hash: str # Skeleton config hash (identity input: VAD config, + split policy when a split stage ran)
|
|
284
360
|
chunk_start: float # VAD chunk start (chunk-local seconds within the rendition WAV)
|
|
285
361
|
chunk_end: float # VAD chunk end (chunk-local seconds)
|
|
286
362
|
index: int # Source-wide fine-spine index (0-based)
|
|
@@ -290,13 +366,18 @@ class SegmentNode:
|
|
|
290
366
|
audio_hash: str = "" # Content hash of the owning rendition's model-input WAV
|
|
291
367
|
source: Optional[str] = None # Source node id (convenience for direct filters)
|
|
292
368
|
text_from: Optional[str] = None # Authoritative Transcript node id (None when text is empty)
|
|
369
|
+
split_policy: Optional[str] = None # Split policy+version that refined the skeleton (None = raw VAD chunks)
|
|
293
370
|
text_slices: List[TranscriptSliceRef] = field(default_factory=list) # All per-transcriber slice refs
|
|
371
|
+
identity_salt: Optional[str] = None # Respine salt (the landed Transcript id): joins the id ONLY (0b4d5cfa (3)); None = original derivation
|
|
372
|
+
superseded_by: Optional[str] = None # Respine op id that retired this segment from the live view (stamped post-hoc; None = live)
|
|
294
373
|
|
|
295
374
|
@property
|
|
296
375
|
def id(self) -> str: # Deterministic node id
|
|
297
|
-
"""Deterministic node id (audio-side identity; shared across transcribers, per-rendition
|
|
376
|
+
"""Deterministic node id (audio-side identity; shared across transcribers, per-rendition;
|
|
377
|
+
forked by `identity_salt` on a chunk respine)."""
|
|
298
378
|
return segment_node_id(self.rendition, self.vad_config_hash,
|
|
299
|
-
self.chunk_start, self.chunk_end
|
|
379
|
+
self.chunk_start, self.chunk_end,
|
|
380
|
+
identity_salt=self.identity_salt)
|
|
300
381
|
|
|
301
382
|
def to_graph_node(self) -> Dict[str, Any]: # Node wire dict
|
|
302
383
|
"""Build the Segment node wire dict (audio ref + per-transcriber text slice refs)."""
|
|
@@ -308,11 +389,18 @@ class SegmentNode:
|
|
|
308
389
|
"chunk_start": self.chunk_start,
|
|
309
390
|
"chunk_end": self.chunk_end,
|
|
310
391
|
"rendition_id": self.rendition,
|
|
392
|
+
"skeleton_hash": self.vad_config_hash,
|
|
311
393
|
}
|
|
394
|
+
if self.split_policy:
|
|
395
|
+
props["split_policy"] = self.split_policy
|
|
312
396
|
if self.source:
|
|
313
397
|
props["source_id"] = self.source
|
|
314
398
|
if self.text_from:
|
|
315
399
|
props["text_from"] = self.text_from
|
|
400
|
+
if self.identity_salt:
|
|
401
|
+
props["identity_salt"] = self.identity_salt
|
|
402
|
+
if self.superseded_by:
|
|
403
|
+
props["superseded_by"] = self.superseded_by
|
|
316
404
|
sources = [SourceRef(locator=GraphNodeRef(node_id=self.rendition),
|
|
317
405
|
content_hash=self.audio_hash,
|
|
318
406
|
slice=TimeSlice(start=self.chunk_start, end=self.chunk_end)).to_dict()]
|
|
@@ -325,6 +413,54 @@ class SegmentNode:
|
|
|
325
413
|
}
|
|
326
414
|
|
|
327
415
|
|
|
416
|
+
RESPINE_OP_VERB = "chunk-respine" # The journaled spine-fact op that lands a chunk respine (0b4d5cfa (4))
|
|
417
|
+
SOURCE_RESPINED_CHUNKS_PROP = "respined_chunks" # Source prop: the list of RespinedChunkEntry dicts, append-only
|
|
418
|
+
SEGMENT_SUPERSEDED_BY_PROP = "superseded_by" # Segment prop stamped on the chunk's old segments (live readers filter it null)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
@dataclass
|
|
422
|
+
class RespinedChunkEntry:
|
|
423
|
+
"""One chunk respine as the Source records it (0b4d5cfa (4)): the entry the
|
|
424
|
+
`chunk-respine` op appends to the Source's `respined_chunks` list — WHICH
|
|
425
|
+
coarse chunk, WHICH landed Transcript re-derived it (= the new segments'
|
|
426
|
+
identity salt), under WHICH decomp run, when, and what the op did to the
|
|
427
|
+
dependents (stranded ids on explicit say-so; carried = the corrections the
|
|
428
|
+
chunk-scoped transfer re-homed by time: accepted event inserts, speaker
|
|
429
|
+
assignments). The op id is what `superseded_by` on the old segments names."""
|
|
430
|
+
|
|
431
|
+
audio_segment: str # AudioSegment node id (the coarse chunk)
|
|
432
|
+
transcript: str # The landed Transcript node id (= identity_salt of the new segments)
|
|
433
|
+
run: str # The decomp run id the re-derivation ran under
|
|
434
|
+
op_id: str # The chunk-respine op id (stamped as superseded_by)
|
|
435
|
+
ts: float # Wall-clock seconds of the landing
|
|
436
|
+
old_segments: List[str] = field(default_factory=list) # The superseded segment ids, index order
|
|
437
|
+
new_segments: List[str] = field(default_factory=list) # The replacement segment ids, index order
|
|
438
|
+
stranded: List[str] = field(default_factory=list) # Dependent Correction ids that stopped projecting (on say-so)
|
|
439
|
+
carried: List[str] = field(default_factory=list) # Fresh Correction ids the transfer minted onto the new segments
|
|
440
|
+
straddles: List[str] = field(default_factory=list) # New segment ids left unassigned because they straddle two donor speakers (4a7ec4f8 (3))
|
|
441
|
+
|
|
442
|
+
def to_dict(self) -> Dict[str, Any]: # The wire entry
|
|
443
|
+
"""The entry as it rides the Source prop (plain JSON, list order preserved)."""
|
|
444
|
+
return {"audio_segment": self.audio_segment, "transcript": self.transcript,
|
|
445
|
+
"run": self.run, "op_id": self.op_id, "ts": self.ts,
|
|
446
|
+
"old_segments": list(self.old_segments), "new_segments": list(self.new_segments),
|
|
447
|
+
"stranded": list(self.stranded), "carried": list(self.carried),
|
|
448
|
+
"straddles": list(self.straddles)}
|
|
449
|
+
|
|
450
|
+
@classmethod
|
|
451
|
+
def from_dict(cls, d: Dict[str, Any]) -> "RespinedChunkEntry": # Parsed entry
|
|
452
|
+
"""Read an entry back off the Source prop (missing lists read empty)."""
|
|
453
|
+
return cls(audio_segment=str(d.get("audio_segment") or ""),
|
|
454
|
+
transcript=str(d.get("transcript") or ""),
|
|
455
|
+
run=str(d.get("run") or ""), op_id=str(d.get("op_id") or ""),
|
|
456
|
+
ts=float(d.get("ts") or 0.0),
|
|
457
|
+
old_segments=[str(s) for s in (d.get("old_segments") or [])],
|
|
458
|
+
new_segments=[str(s) for s in (d.get("new_segments") or [])],
|
|
459
|
+
stranded=[str(s) for s in (d.get("stranded") or [])],
|
|
460
|
+
carried=[str(s) for s in (d.get("carried") or [])],
|
|
461
|
+
straddles=[str(s) for s in (d.get("straddles") or [])])
|
|
462
|
+
|
|
463
|
+
|
|
328
464
|
def normalize_collection_title(
|
|
329
465
|
title: str, # Raw title (folder name, user input, ...)
|
|
330
466
|
) -> str: # Normalized identity form
|
|
@@ -364,6 +500,7 @@ class CollectionNode:
|
|
|
364
500
|
rename, refile out of, or dissolve. root_kind=asserted — a collection is
|
|
365
501
|
a grouping ASSERTION about ingested content, not ingested content itself.
|
|
366
502
|
Collection PART_OF Collection nesting is grammar-free (no v0 tooling)."""
|
|
503
|
+
|
|
367
504
|
title: str # Display title (identity input via normalize_collection_title)
|
|
368
505
|
status: str = "proposed" # "proposed" (tool inference) | "confirmed" (human act)
|
|
369
506
|
actor: str = "human" # Who declared it (attribution; e.g. "human", "tui:transcription", "cli:transcribe")
|
|
@@ -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.12
|
|
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
|
|
@@ -23,7 +23,8 @@ Audio-transcript layer schema for context graphs: Source, AudioSegment, Transcri
|
|
|
23
23
|
|
|
24
24
|
## Modules
|
|
25
25
|
|
|
26
|
-
- **`cjm_transcript_graph_schema.
|
|
26
|
+
- **`cjm_transcript_graph_schema.__init__`**
|
|
27
|
+
- **`cjm_transcript_graph_schema.schema`** — 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, and with the intrinsic Collection layer ABOVE Source (book/series/course; proposed-by-tool / confirmed-by-actor, order optional) captured at transcription hand-off. 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.
|
|
27
28
|
|
|
28
29
|
## API
|
|
29
30
|
|
|
@@ -32,6 +33,7 @@ Audio-transcript layer schema for context graphs: Source, AudioSegment, Transcri
|
|
|
32
33
|
- `AudioRenditionNode` _class_ — A model-input rendition OF an AudioSegment — the materialized 16k-mono WAV
|
|
33
34
|
- `AudioSegmentNode` _class_ — Coarse ~5-min spine member: a BOUNDARY range of the Source (an audio fact),
|
|
34
35
|
- `CollectionNode` _class_ — An intrinsic collection (book, podcast series, lecture course) — the
|
|
36
|
+
- `RespinedChunkEntry` _class_ — One chunk respine as the Source records it (0b4d5cfa (4)): the entry the
|
|
35
37
|
- `SegmentNode` _class_ — Fine spine member: one VAD chunk — IMMUTABLE audio range + CORRECTABLE
|
|
36
38
|
- `SourceNode` _class_ — The provenance root: one ingested media file.
|
|
37
39
|
- `TranscriptGraphLabels` _class_ — Node labels of the audio-transcript layer schema.
|
|
@@ -41,6 +43,8 @@ Audio-transcript layer schema for context graphs: Source, AudioSegment, Transcri
|
|
|
41
43
|
- `audio_segment_node_id` _function_ — AudioSegment identity = (source, boundary range).
|
|
42
44
|
- `collection_edges` _function_ — Membership edges for a collection (ae3464fc: ORDER IS OPTIONAL).
|
|
43
45
|
- `collection_node_id` _function_ — Collection identity = the NORMALIZED TITLE (ratified fork, hub-session
|
|
46
|
+
- `external_config_hash` _function_ — The config hash an external landing carries where a capability's effective
|
|
47
|
+
- `external_transcriber_name` _function_ — Transcriber name for an EXTERNAL landing (ruling 9ffce5f7 (1)): the model id
|
|
44
48
|
- `normalize_collection_title` _function_ — Normalize a collection title for identity derivation.
|
|
45
49
|
- `segment_node_id` _function_ — Fine Segment identity = audio-side only (audio rendition, VAD config, chunk
|
|
46
50
|
- `source_node_id` _function_ — Source identity = the ingested file's content hash (Thread-1 ingested-root identity).
|
|
@@ -49,4 +53,4 @@ Audio-transcript layer schema for context graphs: Source, AudioSegment, Transcri
|
|
|
49
53
|
## Dependencies
|
|
50
54
|
|
|
51
55
|
**Depends on:** `cjm-context-graph-layer`, `cjm-context-graph-primitives`
|
|
52
|
-
**Used by:** `cjm-context-graph-layer`, `cjm-transcript-correction-core`, `cjm-transcript-
|
|
56
|
+
**Used by:** `cjm-context-graph-layer`, `cjm-transcript-correction-core`, `cjm-transcript-decomp-core`, `cjm-transcription-core`, `cjm-transcription-qt`, `cjm-workflow-hub-qt`
|
{cjm_transcript_graph_schema-0.0.8 → cjm_transcript_graph_schema-0.0.12}/tests/test_schema.py
RENAMED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
"""Tests for cjm_transcript_graph_schema.schema — identity tuples + wire shapes.
|
|
2
2
|
|
|
3
3
|
Projected from the schema notebook's four test cells at the c25780e8 flip."""
|
|
4
|
+
|
|
4
5
|
from cjm_context_graph_primitives.provenance import SourceRef
|
|
5
|
-
from cjm_transcript_graph_schema.schema import (
|
|
6
|
-
|
|
7
|
-
TranscriptNode, TranscriptSliceRef,
|
|
8
|
-
audio_rendition_node_id,
|
|
9
|
-
audio_segment_node_id,
|
|
6
|
+
from cjm_transcript_graph_schema.schema import (audio_rendition_node_id, audio_segment_node_id,
|
|
7
|
+
AudioRenditionNode, AudioSegmentNode,
|
|
10
8
|
collection_edges, collection_node_id,
|
|
11
|
-
normalize_collection_title,
|
|
12
|
-
segment_node_id, source_node_id,
|
|
13
|
-
transcript_node_id
|
|
9
|
+
CollectionNode, normalize_collection_title,
|
|
10
|
+
segment_node_id, SegmentNode, source_node_id,
|
|
11
|
+
SourceNode, transcript_node_id, TranscriptNode,
|
|
12
|
+
TranscriptSliceRef)
|
|
14
13
|
|
|
15
14
|
SID = source_node_id("sha256:abc")
|
|
16
15
|
A1 = audio_segment_node_id(SID, 0.0, 300.2)
|
|
@@ -174,3 +173,118 @@ def test_collection_identity_wire_shape_and_edges():
|
|
|
174
173
|
# late-added member attaches to the SAME node without touching the chain
|
|
175
174
|
late = collection_edges(cid, [source_node_id("sha256:ch2.5")])
|
|
176
175
|
assert late[0]["relation_type"] == "PART_OF" and late[0]["target_id"] == cid
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_segment_skeleton_hash_prop_and_split_policy():
|
|
179
|
+
# Every new commit records its skeleton identity input as a QUERYABLE prop
|
|
180
|
+
# (spine-scoped reads filter on it; legacy nodes lack it entirely).
|
|
181
|
+
seg = SegmentNode(rendition=R_RAW, vad_config_hash="sha256:skel", chunk_start=1.0,
|
|
182
|
+
chunk_end=2.0, index=0, start_time=1.0, end_time=2.0, text="hi")
|
|
183
|
+
props = seg.to_graph_node()["properties"]
|
|
184
|
+
assert props["skeleton_hash"] == "sha256:skel"
|
|
185
|
+
assert "split_policy" not in props, "no split stage -> no split_policy prop"
|
|
186
|
+
|
|
187
|
+
split = SegmentNode(rendition=R_RAW, vad_config_hash="sha256:skel2", chunk_start=1.0,
|
|
188
|
+
chunk_end=1.6, index=0, start_time=1.0, end_time=1.6, text="hi",
|
|
189
|
+
split_policy="sentence-split/v1")
|
|
190
|
+
sprops = split.to_graph_node()["properties"]
|
|
191
|
+
assert sprops["skeleton_hash"] == "sha256:skel2"
|
|
192
|
+
assert sprops["split_policy"] == "sentence-split/v1"
|
|
193
|
+
|
|
194
|
+
# split_policy is METADATA, never an identity input — the id stays a pure
|
|
195
|
+
# function of (rendition, skeleton hash, chunk range).
|
|
196
|
+
a = SegmentNode(rendition=R_RAW, vad_config_hash="h", chunk_start=1.0, chunk_end=2.0,
|
|
197
|
+
index=0, start_time=1.0, end_time=2.0)
|
|
198
|
+
b = SegmentNode(rendition=R_RAW, vad_config_hash="h", chunk_start=1.0, chunk_end=2.0,
|
|
199
|
+
index=0, start_time=1.0, end_time=2.0, split_policy="sentence-split/v1")
|
|
200
|
+
assert a.id == b.id
|
|
201
|
+
# ...while the skeleton hash itself DOES fork identity (parallel spines
|
|
202
|
+
# coexist because no node id is shared across skeletons).
|
|
203
|
+
c = SegmentNode(rendition=R_RAW, vad_config_hash="h2", chunk_start=1.0, chunk_end=2.0,
|
|
204
|
+
index=0, start_time=1.0, end_time=2.0)
|
|
205
|
+
assert c.id != a.id
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def test_transcript_supersedes_edge_external_identity_and_operator_attribution():
|
|
209
|
+
"""cf0b91d6 / ruling 910f3692: a re-run's new variant SUPERSEDES the prior variant for
|
|
210
|
+
the same (rendition, transcriber) with a deterministic edge id and refuses a self-edge;
|
|
211
|
+
an EXTERNAL landing files under `<model id>/manual` with a config hash derived from
|
|
212
|
+
(model id, prompt hash) — a different prompt is a different variant — and carries the
|
|
213
|
+
OPERATOR as actor (default attribution stays capability:<transcriber>/transcribe)."""
|
|
214
|
+
import pytest
|
|
215
|
+
from cjm_transcript_graph_schema.schema import (EXTERNAL_TRANSCRIBER_MARKER, external_config_hash,
|
|
216
|
+
external_transcriber_name)
|
|
217
|
+
|
|
218
|
+
old = TranscriptNode(rendition=R_RAW, transcriber="voxtral", config_hash="cfg-old",
|
|
219
|
+
text="some some some", audio_hash="sha256:wav0")
|
|
220
|
+
new = TranscriptNode(rendition=R_RAW, transcriber="voxtral", config_hash="cfg-new",
|
|
221
|
+
text="the good prefix", audio_hash="sha256:wav0")
|
|
222
|
+
assert old.id != new.id
|
|
223
|
+
e = new.supersedes_edge(old.id)
|
|
224
|
+
assert (e["source_id"], e["target_id"], e["relation_type"]) == (new.id, old.id, "SUPERSEDES")
|
|
225
|
+
assert e["id"] == new.supersedes_edge(old.id)["id"], "deterministic edge id"
|
|
226
|
+
with pytest.raises(ValueError):
|
|
227
|
+
new.supersedes_edge(new.id)
|
|
228
|
+
# External landing identity.
|
|
229
|
+
name = external_transcriber_name("gemini-2.5-pro")
|
|
230
|
+
assert name == "gemini-2.5-pro" + EXTERNAL_TRANSCRIBER_MARKER == "gemini-2.5-pro/manual"
|
|
231
|
+
with pytest.raises(ValueError):
|
|
232
|
+
external_transcriber_name(" ")
|
|
233
|
+
h1 = external_config_hash("gemini-2.5-pro", "sha256:prompt-a")
|
|
234
|
+
h2 = external_config_hash("gemini-2.5-pro", "sha256:prompt-b")
|
|
235
|
+
assert h1.startswith("sha256:") and h1 != h2
|
|
236
|
+
assert h1 == external_config_hash("gemini-2.5-pro", "sha256:prompt-a"), "deterministic"
|
|
237
|
+
ext = TranscriptNode(rendition=R_RAW, transcriber=name, config_hash=h1, text="NCCL, not nickel",
|
|
238
|
+
audio_hash="sha256:wav0", actor="human:operator", method="external-landing")
|
|
239
|
+
en = ext.to_graph_node()
|
|
240
|
+
assert en["id"] == transcript_node_id(R_RAW, name, h1)
|
|
241
|
+
assert en["properties"]["actor"] == "human:operator" and en["properties"]["method"] == "external-landing"
|
|
242
|
+
# The default attribution is unchanged for capability runs.
|
|
243
|
+
assert new.to_graph_node()["properties"]["actor"] == "capability:voxtral"
|
|
244
|
+
assert new.to_graph_node()["properties"]["method"] == "transcribe"
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def test_segment_identity_salt_and_superseded_by_respine_props():
|
|
248
|
+
"""0b4d5cfa (3)/(4), schema 0.0.11: a chunk respine forks Segment identity through
|
|
249
|
+
`identity_salt` ONLY — the skeleton_hash prop stays the live spine's, so no reader
|
|
250
|
+
splits the spine; the salt is the landed Transcript id, so the same landing twice
|
|
251
|
+
collides into the same id (idempotent) and a different landing mints fresh ids.
|
|
252
|
+
`superseded_by` rides as a prop only when set (live segments carry none)."""
|
|
253
|
+
from cjm_transcript_graph_schema.schema import (RESPINE_OP_VERB, SEGMENT_SUPERSEDED_BY_PROP,
|
|
254
|
+
SOURCE_RESPINED_CHUNKS_PROP, RespinedChunkEntry)
|
|
255
|
+
|
|
256
|
+
live = SegmentNode(rendition=R_RAW, vad_config_hash="sha256:skel", chunk_start=1.0,
|
|
257
|
+
chunk_end=2.0, index=7, start_time=301.0, end_time=302.0, text="hi")
|
|
258
|
+
t_a = transcript_node_id(R_RAW, "gemini-3.8-flash/manual", "sha256:cfg-a")
|
|
259
|
+
t_b = transcript_node_id(R_RAW, "gemini-3.8-flash/manual", "sha256:cfg-b")
|
|
260
|
+
salted = SegmentNode(rendition=R_RAW, vad_config_hash="sha256:skel", chunk_start=1.0,
|
|
261
|
+
chunk_end=2.0, index=7, start_time=301.0, end_time=302.0, text="hi",
|
|
262
|
+
identity_salt=t_a)
|
|
263
|
+
again = SegmentNode(rendition=R_RAW, vad_config_hash="sha256:skel", chunk_start=1.0,
|
|
264
|
+
chunk_end=2.0, index=7, start_time=301.0, end_time=302.0, text="hi",
|
|
265
|
+
identity_salt=t_a)
|
|
266
|
+
other = SegmentNode(rendition=R_RAW, vad_config_hash="sha256:skel", chunk_start=1.0,
|
|
267
|
+
chunk_end=2.0, index=7, start_time=301.0, end_time=302.0, text="hi",
|
|
268
|
+
identity_salt=t_b)
|
|
269
|
+
assert live.id == segment_node_id(R_RAW, "sha256:skel", 1.0, 2.0), "pre-salt ids unchanged"
|
|
270
|
+
assert live.id == segment_node_id(R_RAW, "sha256:skel", 1.0, 2.0, identity_salt=None)
|
|
271
|
+
assert salted.id != live.id and salted.id == again.id and other.id != salted.id
|
|
272
|
+
assert salted.id == segment_node_id(R_RAW, "sha256:skel", 1.0, 2.0, identity_salt=t_a)
|
|
273
|
+
lp, sp = live.to_graph_node()["properties"], salted.to_graph_node()["properties"]
|
|
274
|
+
assert sp["skeleton_hash"] == lp["skeleton_hash"] == "sha256:skel", "the salt never touches the hash"
|
|
275
|
+
assert sp["identity_salt"] == t_a and "identity_salt" not in lp
|
|
276
|
+
assert SEGMENT_SUPERSEDED_BY_PROP not in lp and SEGMENT_SUPERSEDED_BY_PROP not in sp
|
|
277
|
+
old = SegmentNode(rendition=R_RAW, vad_config_hash="sha256:skel", chunk_start=1.0,
|
|
278
|
+
chunk_end=2.0, index=7, start_time=301.0, end_time=302.0, text="hi",
|
|
279
|
+
superseded_by="op-1")
|
|
280
|
+
assert old.id == live.id, "superseded_by is a stamp, never identity"
|
|
281
|
+
assert old.to_graph_node()["properties"][SEGMENT_SUPERSEDED_BY_PROP] == "op-1"
|
|
282
|
+
# The Source-side record round-trips (missing lists read empty).
|
|
283
|
+
assert (RESPINE_OP_VERB, SOURCE_RESPINED_CHUNKS_PROP) == ("chunk-respine", "respined_chunks")
|
|
284
|
+
entry = RespinedChunkEntry(audio_segment=A1, transcript=t_a, run="run-1", op_id="op-1",
|
|
285
|
+
ts=1.5, old_segments=[live.id], new_segments=[salted.id],
|
|
286
|
+
stranded=["c1"], carried=["c2"], straddles=[salted.id])
|
|
287
|
+
d = entry.to_dict()
|
|
288
|
+
assert RespinedChunkEntry.from_dict(d) == entry
|
|
289
|
+
assert RespinedChunkEntry.from_dict({"audio_segment": A1, "transcript": t_a,
|
|
290
|
+
"run": "r", "op_id": "o", "ts": 2}).stranded == []
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.0.8"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|