cjm-transcript-graph-schema 0.0.7__tar.gz → 0.0.11__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.7 → cjm_transcript_graph_schema-0.0.11}/MANIFEST.in +0 -1
- cjm_transcript_graph_schema-0.0.11/PKG-INFO +53 -0
- cjm_transcript_graph_schema-0.0.11/README.md +36 -0
- cjm_transcript_graph_schema-0.0.11/cjm_transcript_graph_schema/__init__.py +1 -0
- {cjm_transcript_graph_schema-0.0.7 → cjm_transcript_graph_schema-0.0.11}/cjm_transcript_graph_schema/schema.py +240 -35
- cjm_transcript_graph_schema-0.0.11/cjm_transcript_graph_schema.egg-info/PKG-INFO +53 -0
- {cjm_transcript_graph_schema-0.0.7 → cjm_transcript_graph_schema-0.0.11}/cjm_transcript_graph_schema.egg-info/SOURCES.txt +2 -3
- {cjm_transcript_graph_schema-0.0.7 → cjm_transcript_graph_schema-0.0.11}/pyproject.toml +1 -6
- cjm_transcript_graph_schema-0.0.11/tests/test_schema.py +291 -0
- cjm_transcript_graph_schema-0.0.7/PKG-INFO +0 -356
- cjm_transcript_graph_schema-0.0.7/README.md +0 -338
- cjm_transcript_graph_schema-0.0.7/cjm_transcript_graph_schema/__init__.py +0 -1
- cjm_transcript_graph_schema-0.0.7/cjm_transcript_graph_schema/_modidx.py +0 -59
- cjm_transcript_graph_schema-0.0.7/cjm_transcript_graph_schema.egg-info/PKG-INFO +0 -356
- cjm_transcript_graph_schema-0.0.7/cjm_transcript_graph_schema.egg-info/entry_points.txt +0 -2
- {cjm_transcript_graph_schema-0.0.7 → cjm_transcript_graph_schema-0.0.11}/LICENSE +0 -0
- {cjm_transcript_graph_schema-0.0.7 → cjm_transcript_graph_schema-0.0.11}/cjm_transcript_graph_schema.egg-info/dependency_links.txt +0 -0
- {cjm_transcript_graph_schema-0.0.7 → cjm_transcript_graph_schema-0.0.11}/cjm_transcript_graph_schema.egg-info/requires.txt +0 -0
- {cjm_transcript_graph_schema-0.0.7 → cjm_transcript_graph_schema-0.0.11}/cjm_transcript_graph_schema.egg-info/top_level.txt +0 -0
- {cjm_transcript_graph_schema-0.0.7 → cjm_transcript_graph_schema-0.0.11}/setup.cfg +0 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cjm-transcript-graph-schema
|
|
3
|
+
Version: 0.0.11
|
|
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
|
+
Author-email: "Christian J. Mills" <9126128+cj-mills@users.noreply.github.com>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Repository, https://github.com/cj-mills/cjm-transcript-graph-schema
|
|
8
|
+
Keywords: python
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
11
|
+
Requires-Python: >=3.12
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: cjm-context-graph-layer>=0.0.7
|
|
15
|
+
Requires-Dist: cjm-context-graph-primitives>=0.0.12
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# cjm-transcript-graph-schema
|
|
19
|
+
|
|
20
|
+
<!-- generated from the context graph by `cjm-context-graph readme` — do not edit by hand; edit the graph (the urge to hand-edit = move it on-graph) -->
|
|
21
|
+
|
|
22
|
+
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.
|
|
23
|
+
|
|
24
|
+
## Modules
|
|
25
|
+
|
|
26
|
+
- **`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. 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
|
+
## API
|
|
29
|
+
|
|
30
|
+
### `cjm_transcript_graph_schema.schema`
|
|
31
|
+
|
|
32
|
+
- `AudioRenditionNode` _class_ — A model-input rendition OF an AudioSegment — the materialized 16k-mono WAV
|
|
33
|
+
- `AudioSegmentNode` _class_ — Coarse ~5-min spine member: a BOUNDARY range of the Source (an audio fact),
|
|
34
|
+
- `CollectionNode` _class_ — An intrinsic collection (book, podcast series, lecture course) — the
|
|
35
|
+
- `RespinedChunkEntry` _class_ — One chunk respine as the Source records it (0b4d5cfa (4)): the entry the
|
|
36
|
+
- `SegmentNode` _class_ — Fine spine member: one VAD chunk — IMMUTABLE audio range + CORRECTABLE
|
|
37
|
+
- `SourceNode` _class_ — The provenance root: one ingested media file.
|
|
38
|
+
- `TranscriptGraphLabels` _class_ — Node labels of the audio-transcript layer schema.
|
|
39
|
+
- `TranscriptNode` _class_ — One transcriber's text for one AudioRendition (per-transcriber variants at
|
|
40
|
+
- `TranscriptSliceRef` _class_ — One per-transcriber char-range reference for a fine Segment: where this
|
|
41
|
+
- `audio_rendition_node_id` _function_ — AudioRendition identity = (audio segment, preprocessing chain).
|
|
42
|
+
- `audio_segment_node_id` _function_ — AudioSegment identity = (source, boundary range).
|
|
43
|
+
- `collection_edges` _function_ — Membership edges for a collection (ae3464fc: ORDER IS OPTIONAL).
|
|
44
|
+
- `collection_node_id` _function_ — Collection identity = the NORMALIZED TITLE (ratified fork, hub-session
|
|
45
|
+
- `normalize_collection_title` _function_ — Normalize a collection title for identity derivation.
|
|
46
|
+
- `segment_node_id` _function_ — Fine Segment identity = audio-side only (audio rendition, VAD config, chunk
|
|
47
|
+
- `source_node_id` _function_ — Source identity = the ingested file's content hash (Thread-1 ingested-root identity).
|
|
48
|
+
- `transcript_node_id` _function_ — Transcript identity = (audio rendition, transcriber, config) — MIRRORS the
|
|
49
|
+
|
|
50
|
+
## Dependencies
|
|
51
|
+
|
|
52
|
+
**Depends on:** `cjm-context-graph-layer`, `cjm-context-graph-primitives`
|
|
53
|
+
**Used by:** `cjm-context-graph-layer`, `cjm-transcript-correction-core`, `cjm-transcript-correction-tui`, `cjm-transcript-decomp-core`, `cjm-transcription-core`
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# cjm-transcript-graph-schema
|
|
2
|
+
|
|
3
|
+
<!-- generated from the context graph by `cjm-context-graph readme` — do not edit by hand; edit the graph (the urge to hand-edit = move it on-graph) -->
|
|
4
|
+
|
|
5
|
+
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.
|
|
6
|
+
|
|
7
|
+
## Modules
|
|
8
|
+
|
|
9
|
+
- **`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. 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
|
+
## API
|
|
12
|
+
|
|
13
|
+
### `cjm_transcript_graph_schema.schema`
|
|
14
|
+
|
|
15
|
+
- `AudioRenditionNode` _class_ — A model-input rendition OF an AudioSegment — the materialized 16k-mono WAV
|
|
16
|
+
- `AudioSegmentNode` _class_ — Coarse ~5-min spine member: a BOUNDARY range of the Source (an audio fact),
|
|
17
|
+
- `CollectionNode` _class_ — An intrinsic collection (book, podcast series, lecture course) — the
|
|
18
|
+
- `RespinedChunkEntry` _class_ — One chunk respine as the Source records it (0b4d5cfa (4)): the entry the
|
|
19
|
+
- `SegmentNode` _class_ — Fine spine member: one VAD chunk — IMMUTABLE audio range + CORRECTABLE
|
|
20
|
+
- `SourceNode` _class_ — The provenance root: one ingested media file.
|
|
21
|
+
- `TranscriptGraphLabels` _class_ — Node labels of the audio-transcript layer schema.
|
|
22
|
+
- `TranscriptNode` _class_ — One transcriber's text for one AudioRendition (per-transcriber variants at
|
|
23
|
+
- `TranscriptSliceRef` _class_ — One per-transcriber char-range reference for a fine Segment: where this
|
|
24
|
+
- `audio_rendition_node_id` _function_ — AudioRendition identity = (audio segment, preprocessing chain).
|
|
25
|
+
- `audio_segment_node_id` _function_ — AudioSegment identity = (source, boundary range).
|
|
26
|
+
- `collection_edges` _function_ — Membership edges for a collection (ae3464fc: ORDER IS OPTIONAL).
|
|
27
|
+
- `collection_node_id` _function_ — Collection identity = the NORMALIZED TITLE (ratified fork, hub-session
|
|
28
|
+
- `normalize_collection_title` _function_ — Normalize a collection title for identity derivation.
|
|
29
|
+
- `segment_node_id` _function_ — Fine Segment identity = audio-side only (audio rendition, VAD config, chunk
|
|
30
|
+
- `source_node_id` _function_ — Source identity = the ingested file's content hash (Thread-1 ingested-root identity).
|
|
31
|
+
- `transcript_node_id` _function_ — Transcript identity = (audio rendition, transcriber, config) — MIRRORS the
|
|
32
|
+
|
|
33
|
+
## Dependencies
|
|
34
|
+
|
|
35
|
+
**Depends on:** `cjm-context-graph-layer`, `cjm-context-graph-primitives`
|
|
36
|
+
**Used by:** `cjm-context-graph-layer`, `cjm-transcript-correction-core`, `cjm-transcript-correction-tui`, `cjm-transcript-decomp-core`, `cjm-transcription-core`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.11"
|
|
@@ -1,26 +1,17 @@
|
|
|
1
|
-
"""The audio-transcript layer schema (where-graph-begins locked layer schema):
|
|
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, 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."""
|
|
2
2
|
|
|
3
|
-
Docs: https://cj-mills.github.io/cjm-transcript-graph-schemaschema.html.md"""
|
|
4
|
-
|
|
5
|
-
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/schema.ipynb.
|
|
6
|
-
|
|
7
|
-
# %% auto #0
|
|
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']
|
|
11
|
-
|
|
12
|
-
# %% ../nbs/schema.ipynb #76d0dd1c
|
|
13
|
-
from pathlib import Path
|
|
14
3
|
from dataclasses import dataclass, field
|
|
4
|
+
from pathlib import Path
|
|
15
5
|
from typing import Any, Dict, List, Optional
|
|
16
6
|
|
|
7
|
+
from cjm_context_graph_layer.grammar import (attribution, make_edge, OverlayRelations, spine_edges,
|
|
8
|
+
SpineRelations)
|
|
17
9
|
from cjm_context_graph_layer.identity import derive_node_id
|
|
18
|
-
from cjm_context_graph_layer.grammar import OverlayRelations, attribution, make_edge
|
|
19
10
|
from cjm_context_graph_primitives.locators import FileRef, GraphNodeRef
|
|
20
|
-
from cjm_context_graph_primitives.slices import TimeSlice, CharSlice, FullContent
|
|
21
11
|
from cjm_context_graph_primitives.provenance import SourceRef
|
|
12
|
+
from cjm_context_graph_primitives.slices import CharSlice, FullContent, TimeSlice
|
|
13
|
+
|
|
22
14
|
|
|
23
|
-
# %% ../nbs/schema.ipynb #ff94773a
|
|
24
15
|
class TranscriptGraphLabels:
|
|
25
16
|
"""Node labels of the audio-transcript layer schema."""
|
|
26
17
|
SOURCE = "Source" # Provenance root: one ingested media file
|
|
@@ -28,20 +19,23 @@ class TranscriptGraphLabels:
|
|
|
28
19
|
AUDIO_RENDITION = "AudioRendition" # A model-input rendition OF an AudioSegment (raw convert | vocals | future enhancement); OWNS the model-input WAV
|
|
29
20
|
TRANSCRIPT = "Transcript" # Per-transcriber text variant of one AudioRendition
|
|
30
21
|
SEGMENT = "Segment" # Fine spine member: one VAD chunk (immutable audio + correctable text), per-rendition
|
|
22
|
+
COLLECTION = "Collection" # Intrinsic collection (book / series / course): the fractal spine layer ABOVE Source
|
|
23
|
+
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)
|
|
31
24
|
|
|
32
25
|
@classmethod
|
|
33
26
|
def all(cls) -> list: # All schema labels
|
|
34
27
|
"""All schema labels."""
|
|
35
|
-
return [cls.SOURCE, cls.AUDIO_SEGMENT, cls.AUDIO_RENDITION, cls.TRANSCRIPT, cls.SEGMENT
|
|
28
|
+
return [cls.SOURCE, cls.AUDIO_SEGMENT, cls.AUDIO_RENDITION, cls.TRANSCRIPT, cls.SEGMENT, cls.COLLECTION,
|
|
29
|
+
cls.REFERENCE]
|
|
30
|
+
|
|
36
31
|
|
|
37
|
-
# %% ../nbs/schema.ipynb #a5ea12db
|
|
38
32
|
def source_node_id(
|
|
39
33
|
content_hash: str, # Content hash of the source media file ("algo:hexdigest")
|
|
40
34
|
) -> str: # Deterministic Source node id
|
|
41
35
|
"""Source identity = the ingested file's content hash (Thread-1 ingested-root identity)."""
|
|
42
36
|
return derive_node_id("source", content_hash)
|
|
43
37
|
|
|
44
|
-
|
|
38
|
+
|
|
45
39
|
def audio_segment_node_id(
|
|
46
40
|
source_id: str, # Owning Source node id
|
|
47
41
|
start: float, # Boundary start (source-coordinate seconds)
|
|
@@ -55,7 +49,7 @@ def audio_segment_node_id(
|
|
|
55
49
|
the id; the model-input WAV lives on the `AudioRendition` child, not here."""
|
|
56
50
|
return derive_node_id("audio-segment", source_id, start, end)
|
|
57
51
|
|
|
58
|
-
|
|
52
|
+
|
|
59
53
|
def audio_rendition_node_id(
|
|
60
54
|
audio_segment_id: str, # Owning AudioSegment node id
|
|
61
55
|
chain: List[str], # Ordered preprocessing-chain descriptors; [] = the raw convert-only rendition
|
|
@@ -73,7 +67,7 @@ def audio_rendition_node_id(
|
|
|
73
67
|
search)."""
|
|
74
68
|
return derive_node_id("audio-rendition", audio_segment_id, *chain)
|
|
75
69
|
|
|
76
|
-
|
|
70
|
+
|
|
77
71
|
def transcript_node_id(
|
|
78
72
|
rendition_id: str, # Owning AudioRendition node id
|
|
79
73
|
transcriber: str, # Transcriber capability name (e.g. "cjm-transcription-plugin-whisper")
|
|
@@ -85,21 +79,65 @@ def transcript_node_id(
|
|
|
85
79
|
RENDITION, so a raw vs vocals transcript of the same boundary are distinct."""
|
|
86
80
|
return derive_node_id("transcript", rendition_id, transcriber, config_hash)
|
|
87
81
|
|
|
88
|
-
|
|
82
|
+
|
|
83
|
+
EXTERNAL_TRANSCRIBER_MARKER = "/manual" # Suffix naming an operator-landed (pasted) transcript producer
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def external_transcriber_name(
|
|
87
|
+
model_id: str, # The external model that produced the text (e.g. "gemini-2.5-pro")
|
|
88
|
+
) -> str: # The transcriber name an external landing files under
|
|
89
|
+
"""Transcriber name for an EXTERNAL landing (ruling 9ffce5f7 (1)): the model id
|
|
90
|
+
plus a manual marker, so a pasted transcript is a THIRD TRANSCRIBER beside the
|
|
91
|
+
local capabilities — decomp folds it per chunk like any other variant, and the
|
|
92
|
+
marker keeps it distinguishable from a capability run of the same model."""
|
|
93
|
+
mid = (model_id or "").strip()
|
|
94
|
+
if not mid:
|
|
95
|
+
raise ValueError("external_transcriber_name: model_id is required")
|
|
96
|
+
return mid + EXTERNAL_TRANSCRIBER_MARKER
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def external_config_hash(
|
|
100
|
+
model_id: str, # The external model id
|
|
101
|
+
prompt_hash: str, # Hash of the prompt template the operator used ("" = no prompt recorded)
|
|
102
|
+
) -> str: # "sha256:<hex>" — the config-hash slot of an external landing
|
|
103
|
+
"""The config hash an external landing carries where a capability's effective
|
|
104
|
+
config hash goes: sha256 over (model id, prompt hash). The prompt is DATA
|
|
105
|
+
(f304d31d), so a different prompt template is a different variant — and the
|
|
106
|
+
Transcript identity (rendition, transcriber, config_hash) stays recomputable
|
|
107
|
+
from the derived manifest alone."""
|
|
108
|
+
import hashlib
|
|
109
|
+
h = hashlib.sha256()
|
|
110
|
+
h.update((model_id or "").strip().encode("utf-8"))
|
|
111
|
+
h.update(b"\n")
|
|
112
|
+
h.update((prompt_hash or "").strip().encode("utf-8"))
|
|
113
|
+
return "sha256:" + h.hexdigest()
|
|
114
|
+
|
|
115
|
+
|
|
89
116
|
def segment_node_id(
|
|
90
117
|
rendition_id: str, # Owning AudioRendition node id
|
|
91
|
-
vad_config_hash: str, #
|
|
118
|
+
vad_config_hash: str, # Skeleton config hash (identity input: VAD config, + split policy when a split stage ran)
|
|
92
119
|
chunk_start: float, # VAD chunk start (chunk-local seconds within the rendition WAV)
|
|
93
120
|
chunk_end: float, # VAD chunk end (chunk-local seconds)
|
|
121
|
+
identity_salt: Optional[str] = None, # Respine salt (0b4d5cfa (3)): joins the id ONLY; None = the spine's original derivation
|
|
94
122
|
) -> str: # Deterministic Segment node id
|
|
95
123
|
"""Fine Segment identity = audio-side only (audio rendition, VAD config, chunk
|
|
96
124
|
range) — so the skeleton's ids are SHARED across transcribers by
|
|
97
125
|
construction (C4 "store agreement once" falls out of identity design). Keyed
|
|
98
126
|
on the RENDITION: each rendition has its own fine spine (vocals isolation can
|
|
99
|
-
yield different VAD chunk boundaries than raw).
|
|
127
|
+
yield different VAD chunk boundaries than raw).
|
|
128
|
+
|
|
129
|
+
A CHUNK RESPINE (0b4d5cfa) re-derives one coarse chunk into the LIVE spine
|
|
130
|
+
from a landed external transcript: its segments keep the live skeleton hash
|
|
131
|
+
(every picker groups spines by it) and fork identity through `identity_salt`
|
|
132
|
+
= the landed Transcript's node id, so the same landing twice collides into
|
|
133
|
+
a no-op and a different landing mints fresh segments. None (the default)
|
|
134
|
+
reproduces every pre-salt id unchanged."""
|
|
135
|
+
if identity_salt:
|
|
136
|
+
return derive_node_id("segment", rendition_id, vad_config_hash, chunk_start, chunk_end,
|
|
137
|
+
identity_salt)
|
|
100
138
|
return derive_node_id("segment", rendition_id, vad_config_hash, chunk_start, chunk_end)
|
|
101
139
|
|
|
102
|
-
|
|
140
|
+
|
|
103
141
|
@dataclass
|
|
104
142
|
class SourceNode:
|
|
105
143
|
"""The provenance root: one ingested media file."""
|
|
@@ -128,7 +166,7 @@ class SourceNode:
|
|
|
128
166
|
content_hash=self.content_hash).to_dict()],
|
|
129
167
|
}
|
|
130
168
|
|
|
131
|
-
|
|
169
|
+
|
|
132
170
|
@dataclass
|
|
133
171
|
class AudioSegmentNode:
|
|
134
172
|
"""Coarse ~5-min spine member: a BOUNDARY range of the Source (an audio fact),
|
|
@@ -169,7 +207,7 @@ class AudioSegmentNode:
|
|
|
169
207
|
"sources": [],
|
|
170
208
|
}
|
|
171
209
|
|
|
172
|
-
|
|
210
|
+
|
|
173
211
|
@dataclass
|
|
174
212
|
class AudioRenditionNode:
|
|
175
213
|
"""A model-input rendition OF an AudioSegment — the materialized 16k-mono WAV
|
|
@@ -216,7 +254,7 @@ class AudioRenditionNode:
|
|
|
216
254
|
"""DERIVED_FROM edge: this rendition derives from its AudioSegment's audio."""
|
|
217
255
|
return make_edge(self.id, self.audio_segment, OverlayRelations.DERIVED_FROM)
|
|
218
256
|
|
|
219
|
-
|
|
257
|
+
|
|
220
258
|
@dataclass
|
|
221
259
|
class TranscriptNode:
|
|
222
260
|
"""One transcriber's text for one AudioRendition (per-transcriber variants at
|
|
@@ -228,6 +266,8 @@ class TranscriptNode:
|
|
|
228
266
|
audio_hash: str # Content hash of the consumed model-input WAV (the rendition's)
|
|
229
267
|
metadata: Dict[str, Any] = field(default_factory=dict) # Transcriber-reported metadata
|
|
230
268
|
asserted_at: Optional[float] = None # Derivation timestamp; None = now
|
|
269
|
+
actor: Optional[str] = None # Attribution actor; None = "capability:<transcriber>" (an EXTERNAL landing names the operator, 9ffce5f7 (1))
|
|
270
|
+
method: str = "transcribe" # Attribution method ("transcribe" for a capability run; "external-landing" for pasted text)
|
|
231
271
|
|
|
232
272
|
@property
|
|
233
273
|
def id(self) -> str: # Deterministic node id
|
|
@@ -244,7 +284,7 @@ class TranscriptNode:
|
|
|
244
284
|
}
|
|
245
285
|
if self.metadata:
|
|
246
286
|
props["metadata"] = dict(self.metadata)
|
|
247
|
-
props.update(attribution(f"capability:{self.transcriber}", method=
|
|
287
|
+
props.update(attribution(self.actor or f"capability:{self.transcriber}", method=self.method,
|
|
248
288
|
asserted_at=self.asserted_at))
|
|
249
289
|
return {
|
|
250
290
|
"id": self.id,
|
|
@@ -259,7 +299,19 @@ class TranscriptNode:
|
|
|
259
299
|
"""DERIVED_FROM edge: this Transcript derives from its AudioRendition."""
|
|
260
300
|
return make_edge(self.id, self.rendition, OverlayRelations.DERIVED_FROM)
|
|
261
301
|
|
|
262
|
-
|
|
302
|
+
def supersedes_edge(
|
|
303
|
+
self,
|
|
304
|
+
prior_transcript_id: str, # The Transcript variant this one replaces (same rendition + transcriber, an older config)
|
|
305
|
+
) -> Dict[str, Any]: # Edge wire dict
|
|
306
|
+
"""SUPERSEDES edge: this variant replaces a prior one for the same (rendition,
|
|
307
|
+
transcriber) — a chunk-targeted re-run under a new config, or a re-landed
|
|
308
|
+
external transcript (ruling 910f3692 (2)). The prior node STAYS as append-only
|
|
309
|
+
history; readers treat a variant with an incoming SUPERSEDES edge as NOT LIVE."""
|
|
310
|
+
if prior_transcript_id == self.id:
|
|
311
|
+
raise ValueError("supersedes_edge: a Transcript cannot supersede itself")
|
|
312
|
+
return make_edge(self.id, prior_transcript_id, OverlayRelations.SUPERSEDES)
|
|
313
|
+
|
|
314
|
+
|
|
263
315
|
@dataclass
|
|
264
316
|
class TranscriptSliceRef:
|
|
265
317
|
"""One per-transcriber char-range reference for a fine Segment: where this
|
|
@@ -276,19 +328,28 @@ class TranscriptSliceRef:
|
|
|
276
328
|
content_hash=SourceRef.compute_hash(self.text.encode()),
|
|
277
329
|
slice=CharSlice(start=self.start_char, end=self.end_char)).to_dict()
|
|
278
330
|
|
|
279
|
-
|
|
331
|
+
|
|
280
332
|
@dataclass
|
|
281
333
|
class SegmentNode:
|
|
282
334
|
"""Fine spine member: one VAD chunk — IMMUTABLE audio range + CORRECTABLE
|
|
283
335
|
text (the immutable-audio/mutable-text spine seam). PART_OF its AudioRendition
|
|
284
|
-
(each rendition has its own fine spine).
|
|
336
|
+
(each rendition has its own fine spine). Parallel SPINES coexist under one
|
|
337
|
+
rendition (sentence-split, DEC f1024568): the skeleton hash feeds every node
|
|
338
|
+
id AND rides the queryable `skeleton_hash` prop, so spine-scoped reads never
|
|
339
|
+
mix coexisting skeletons (pre-split nodes lack the prop = the legacy spine).
|
|
285
340
|
|
|
286
341
|
Layer-0 `text` is the ACCURACY transcriber's alignment; the designation is
|
|
287
342
|
per-segment provenance, not global config (`text_from` names the
|
|
288
343
|
authoritative Transcript; every transcriber's char range rides
|
|
289
|
-
`text_slices`, the authoritative one included).
|
|
344
|
+
`text_slices`, the authoritative one included).
|
|
345
|
+
|
|
346
|
+
A CHUNK RESPINE (0b4d5cfa) replaces one coarse chunk's segments INSIDE the
|
|
347
|
+
live spine: the new segments carry `identity_salt` (joins the id only —
|
|
348
|
+
`skeleton_hash` stays the live spine's, so no reader splits the spine),
|
|
349
|
+
the old ones are stamped `superseded_by` (the respine op id) and leave the
|
|
350
|
+
live view — readers filter `superseded_by is null`; nothing is deleted."""
|
|
290
351
|
rendition: str # Owning AudioRendition node id
|
|
291
|
-
vad_config_hash: str #
|
|
352
|
+
vad_config_hash: str # Skeleton config hash (identity input: VAD config, + split policy when a split stage ran)
|
|
292
353
|
chunk_start: float # VAD chunk start (chunk-local seconds within the rendition WAV)
|
|
293
354
|
chunk_end: float # VAD chunk end (chunk-local seconds)
|
|
294
355
|
index: int # Source-wide fine-spine index (0-based)
|
|
@@ -298,13 +359,18 @@ class SegmentNode:
|
|
|
298
359
|
audio_hash: str = "" # Content hash of the owning rendition's model-input WAV
|
|
299
360
|
source: Optional[str] = None # Source node id (convenience for direct filters)
|
|
300
361
|
text_from: Optional[str] = None # Authoritative Transcript node id (None when text is empty)
|
|
362
|
+
split_policy: Optional[str] = None # Split policy+version that refined the skeleton (None = raw VAD chunks)
|
|
301
363
|
text_slices: List[TranscriptSliceRef] = field(default_factory=list) # All per-transcriber slice refs
|
|
364
|
+
identity_salt: Optional[str] = None # Respine salt (the landed Transcript id): joins the id ONLY (0b4d5cfa (3)); None = original derivation
|
|
365
|
+
superseded_by: Optional[str] = None # Respine op id that retired this segment from the live view (stamped post-hoc; None = live)
|
|
302
366
|
|
|
303
367
|
@property
|
|
304
368
|
def id(self) -> str: # Deterministic node id
|
|
305
|
-
"""Deterministic node id (audio-side identity; shared across transcribers, per-rendition
|
|
369
|
+
"""Deterministic node id (audio-side identity; shared across transcribers, per-rendition;
|
|
370
|
+
forked by `identity_salt` on a chunk respine)."""
|
|
306
371
|
return segment_node_id(self.rendition, self.vad_config_hash,
|
|
307
|
-
self.chunk_start, self.chunk_end
|
|
372
|
+
self.chunk_start, self.chunk_end,
|
|
373
|
+
identity_salt=self.identity_salt)
|
|
308
374
|
|
|
309
375
|
def to_graph_node(self) -> Dict[str, Any]: # Node wire dict
|
|
310
376
|
"""Build the Segment node wire dict (audio ref + per-transcriber text slice refs)."""
|
|
@@ -316,11 +382,18 @@ class SegmentNode:
|
|
|
316
382
|
"chunk_start": self.chunk_start,
|
|
317
383
|
"chunk_end": self.chunk_end,
|
|
318
384
|
"rendition_id": self.rendition,
|
|
385
|
+
"skeleton_hash": self.vad_config_hash,
|
|
319
386
|
}
|
|
387
|
+
if self.split_policy:
|
|
388
|
+
props["split_policy"] = self.split_policy
|
|
320
389
|
if self.source:
|
|
321
390
|
props["source_id"] = self.source
|
|
322
391
|
if self.text_from:
|
|
323
392
|
props["text_from"] = self.text_from
|
|
393
|
+
if self.identity_salt:
|
|
394
|
+
props["identity_salt"] = self.identity_salt
|
|
395
|
+
if self.superseded_by:
|
|
396
|
+
props["superseded_by"] = self.superseded_by
|
|
324
397
|
sources = [SourceRef(locator=GraphNodeRef(node_id=self.rendition),
|
|
325
398
|
content_hash=self.audio_hash,
|
|
326
399
|
slice=TimeSlice(start=self.chunk_start, end=self.chunk_end)).to_dict()]
|
|
@@ -331,3 +404,135 @@ class SegmentNode:
|
|
|
331
404
|
"properties": props,
|
|
332
405
|
"sources": sources,
|
|
333
406
|
}
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
RESPINE_OP_VERB = "chunk-respine" # The journaled spine-fact op that lands a chunk respine (0b4d5cfa (4))
|
|
410
|
+
SOURCE_RESPINED_CHUNKS_PROP = "respined_chunks" # Source prop: the list of RespinedChunkEntry dicts, append-only
|
|
411
|
+
SEGMENT_SUPERSEDED_BY_PROP = "superseded_by" # Segment prop stamped on the chunk's old segments (live readers filter it null)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
@dataclass
|
|
415
|
+
class RespinedChunkEntry:
|
|
416
|
+
"""One chunk respine as the Source records it (0b4d5cfa (4)): the entry the
|
|
417
|
+
`chunk-respine` op appends to the Source's `respined_chunks` list — WHICH
|
|
418
|
+
coarse chunk, WHICH landed Transcript re-derived it (= the new segments'
|
|
419
|
+
identity salt), under WHICH decomp run, when, and what the op did to the
|
|
420
|
+
dependents (stranded ids on explicit say-so; carried = the corrections the
|
|
421
|
+
chunk-scoped transfer re-homed by time: accepted event inserts, speaker
|
|
422
|
+
assignments). The op id is what `superseded_by` on the old segments names."""
|
|
423
|
+
audio_segment: str # AudioSegment node id (the coarse chunk)
|
|
424
|
+
transcript: str # The landed Transcript node id (= identity_salt of the new segments)
|
|
425
|
+
run: str # The decomp run id the re-derivation ran under
|
|
426
|
+
op_id: str # The chunk-respine op id (stamped as superseded_by)
|
|
427
|
+
ts: float # Wall-clock seconds of the landing
|
|
428
|
+
old_segments: List[str] = field(default_factory=list) # The superseded segment ids, index order
|
|
429
|
+
new_segments: List[str] = field(default_factory=list) # The replacement segment ids, index order
|
|
430
|
+
stranded: List[str] = field(default_factory=list) # Dependent Correction ids that stopped projecting (on say-so)
|
|
431
|
+
carried: List[str] = field(default_factory=list) # Fresh Correction ids the transfer minted onto the new segments
|
|
432
|
+
straddles: List[str] = field(default_factory=list) # New segment ids left unassigned because they straddle two donor speakers (4a7ec4f8 (3))
|
|
433
|
+
|
|
434
|
+
def to_dict(self) -> Dict[str, Any]: # The wire entry
|
|
435
|
+
"""The entry as it rides the Source prop (plain JSON, list order preserved)."""
|
|
436
|
+
return {"audio_segment": self.audio_segment, "transcript": self.transcript,
|
|
437
|
+
"run": self.run, "op_id": self.op_id, "ts": self.ts,
|
|
438
|
+
"old_segments": list(self.old_segments), "new_segments": list(self.new_segments),
|
|
439
|
+
"stranded": list(self.stranded), "carried": list(self.carried),
|
|
440
|
+
"straddles": list(self.straddles)}
|
|
441
|
+
|
|
442
|
+
@classmethod
|
|
443
|
+
def from_dict(cls, d: Dict[str, Any]) -> "RespinedChunkEntry": # Parsed entry
|
|
444
|
+
"""Read an entry back off the Source prop (missing lists read empty)."""
|
|
445
|
+
return cls(audio_segment=str(d.get("audio_segment") or ""),
|
|
446
|
+
transcript=str(d.get("transcript") or ""),
|
|
447
|
+
run=str(d.get("run") or ""), op_id=str(d.get("op_id") or ""),
|
|
448
|
+
ts=float(d.get("ts") or 0.0),
|
|
449
|
+
old_segments=[str(s) for s in (d.get("old_segments") or [])],
|
|
450
|
+
new_segments=[str(s) for s in (d.get("new_segments") or [])],
|
|
451
|
+
stranded=[str(s) for s in (d.get("stranded") or [])],
|
|
452
|
+
carried=[str(s) for s in (d.get("carried") or [])],
|
|
453
|
+
straddles=[str(s) for s in (d.get("straddles") or [])])
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def normalize_collection_title(
|
|
457
|
+
title: str, # Raw title (folder name, user input, ...)
|
|
458
|
+
) -> str: # Normalized identity form
|
|
459
|
+
"""Normalize a collection title for identity derivation.
|
|
460
|
+
|
|
461
|
+
Casefold + underscores-to-spaces + whitespace collapse, so a folder named
|
|
462
|
+
``Hardcore_History`` and a hand-typed ``Hardcore History`` converge on the
|
|
463
|
+
SAME collection node (ae3464fc: select-existing and retype-the-title are
|
|
464
|
+
the same act by construction). Display keeps the raw title; only identity
|
|
465
|
+
normalizes."""
|
|
466
|
+
return " ".join(title.replace("_", " ").split()).casefold()
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def collection_node_id(
|
|
470
|
+
title: str, # Collection title (normalized internally)
|
|
471
|
+
) -> str: # Deterministic Collection node id
|
|
472
|
+
"""Collection identity = the NORMALIZED TITLE (ratified fork, hub-session
|
|
473
|
+
2026-07-21): path-based identity breaks workspace relocatability, and
|
|
474
|
+
member-hash identity breaks late-added members — late binding is a feature.
|
|
475
|
+
The same book ingested twice (different paths, different machines, members
|
|
476
|
+
accreted over weeks) is the same collection; rare title collisions are
|
|
477
|
+
propose/confirm reconciler territory."""
|
|
478
|
+
return derive_node_id("collection", normalize_collection_title(title))
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
@dataclass
|
|
482
|
+
class CollectionNode:
|
|
483
|
+
"""An intrinsic collection (book, podcast series, lecture course) — the
|
|
484
|
+
fractal spine layer ABOVE Source (the grammar's "series -> episode
|
|
485
|
+
tomorrow", arrived).
|
|
486
|
+
|
|
487
|
+
Status rides the node (ae3464fc actor criterion): a human who accepted,
|
|
488
|
+
renamed, or selected the collection at run launch has CONFIRMED it;
|
|
489
|
+
untouched tool defaults (headless folder run) land PROPOSED — visible and
|
|
490
|
+
grouping-usable everywhere, flagged as awaiting a human. A folder is
|
|
491
|
+
EVIDENCE of a grouping, not proof: proposed collections are cheap to
|
|
492
|
+
rename, refile out of, or dissolve. root_kind=asserted — a collection is
|
|
493
|
+
a grouping ASSERTION about ingested content, not ingested content itself.
|
|
494
|
+
Collection PART_OF Collection nesting is grammar-free (no v0 tooling)."""
|
|
495
|
+
title: str # Display title (identity input via normalize_collection_title)
|
|
496
|
+
status: str = "proposed" # "proposed" (tool inference) | "confirmed" (human act)
|
|
497
|
+
actor: str = "human" # Who declared it (attribution; e.g. "human", "tui:transcription", "cli:transcribe")
|
|
498
|
+
asserted_at: Optional[float] = None # Declaration timestamp; None = now
|
|
499
|
+
|
|
500
|
+
@property
|
|
501
|
+
def id(self) -> str: # Deterministic node id
|
|
502
|
+
"""Deterministic node id."""
|
|
503
|
+
return collection_node_id(self.title)
|
|
504
|
+
|
|
505
|
+
def to_graph_node(self) -> Dict[str, Any]: # Node wire dict
|
|
506
|
+
"""Build the Collection node wire dict (asserted root; attribution included)."""
|
|
507
|
+
props: Dict[str, Any] = {
|
|
508
|
+
"title": self.title,
|
|
509
|
+
"status": self.status,
|
|
510
|
+
"root_kind": "asserted",
|
|
511
|
+
}
|
|
512
|
+
props.update(attribution(self.actor, method="collection-declaration",
|
|
513
|
+
asserted_at=self.asserted_at))
|
|
514
|
+
return {
|
|
515
|
+
"id": self.id,
|
|
516
|
+
"label": TranscriptGraphLabels.COLLECTION,
|
|
517
|
+
"properties": props,
|
|
518
|
+
"sources": [],
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def collection_edges(
|
|
523
|
+
collection_id: str, # Collection node id
|
|
524
|
+
member_ids: List[str], # Member Source node ids (declaration order when ordered=True)
|
|
525
|
+
ordered: bool = False, # True = materialize the order (NEXT chain + STARTS_WITH)
|
|
526
|
+
) -> List[Dict[str, Any]]: # Edge wire dicts
|
|
527
|
+
"""Membership edges for a collection (ae3464fc: ORDER IS OPTIONAL).
|
|
528
|
+
|
|
529
|
+
Unordered membership is just PART_OF member->collection — individually
|
|
530
|
+
accreted members (chapters run one at a time) attach without fabricating a
|
|
531
|
+
sequence. When an order IS known (a folder run's sorted expansion, or a
|
|
532
|
+
curation act), the full fractal spine pattern materializes via
|
|
533
|
+
spine_edges: PART_OF + NEXT chain + STARTS_WITH — identical grammar to
|
|
534
|
+
every layer below. Reordering and late insertion are curation ops, never
|
|
535
|
+
capture-time guesses."""
|
|
536
|
+
if ordered:
|
|
537
|
+
return spine_edges(collection_id, member_ids)
|
|
538
|
+
return [make_edge(m, collection_id, SpineRelations.PART_OF) for m in member_ids]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cjm-transcript-graph-schema
|
|
3
|
+
Version: 0.0.11
|
|
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
|
+
Author-email: "Christian J. Mills" <9126128+cj-mills@users.noreply.github.com>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Repository, https://github.com/cj-mills/cjm-transcript-graph-schema
|
|
8
|
+
Keywords: python
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
11
|
+
Requires-Python: >=3.12
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: cjm-context-graph-layer>=0.0.7
|
|
15
|
+
Requires-Dist: cjm-context-graph-primitives>=0.0.12
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# cjm-transcript-graph-schema
|
|
19
|
+
|
|
20
|
+
<!-- generated from the context graph by `cjm-context-graph readme` — do not edit by hand; edit the graph (the urge to hand-edit = move it on-graph) -->
|
|
21
|
+
|
|
22
|
+
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.
|
|
23
|
+
|
|
24
|
+
## Modules
|
|
25
|
+
|
|
26
|
+
- **`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. 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
|
+
## API
|
|
29
|
+
|
|
30
|
+
### `cjm_transcript_graph_schema.schema`
|
|
31
|
+
|
|
32
|
+
- `AudioRenditionNode` _class_ — A model-input rendition OF an AudioSegment — the materialized 16k-mono WAV
|
|
33
|
+
- `AudioSegmentNode` _class_ — Coarse ~5-min spine member: a BOUNDARY range of the Source (an audio fact),
|
|
34
|
+
- `CollectionNode` _class_ — An intrinsic collection (book, podcast series, lecture course) — the
|
|
35
|
+
- `RespinedChunkEntry` _class_ — One chunk respine as the Source records it (0b4d5cfa (4)): the entry the
|
|
36
|
+
- `SegmentNode` _class_ — Fine spine member: one VAD chunk — IMMUTABLE audio range + CORRECTABLE
|
|
37
|
+
- `SourceNode` _class_ — The provenance root: one ingested media file.
|
|
38
|
+
- `TranscriptGraphLabels` _class_ — Node labels of the audio-transcript layer schema.
|
|
39
|
+
- `TranscriptNode` _class_ — One transcriber's text for one AudioRendition (per-transcriber variants at
|
|
40
|
+
- `TranscriptSliceRef` _class_ — One per-transcriber char-range reference for a fine Segment: where this
|
|
41
|
+
- `audio_rendition_node_id` _function_ — AudioRendition identity = (audio segment, preprocessing chain).
|
|
42
|
+
- `audio_segment_node_id` _function_ — AudioSegment identity = (source, boundary range).
|
|
43
|
+
- `collection_edges` _function_ — Membership edges for a collection (ae3464fc: ORDER IS OPTIONAL).
|
|
44
|
+
- `collection_node_id` _function_ — Collection identity = the NORMALIZED TITLE (ratified fork, hub-session
|
|
45
|
+
- `normalize_collection_title` _function_ — Normalize a collection title for identity derivation.
|
|
46
|
+
- `segment_node_id` _function_ — Fine Segment identity = audio-side only (audio rendition, VAD config, chunk
|
|
47
|
+
- `source_node_id` _function_ — Source identity = the ingested file's content hash (Thread-1 ingested-root identity).
|
|
48
|
+
- `transcript_node_id` _function_ — Transcript identity = (audio rendition, transcriber, config) — MIRRORS the
|
|
49
|
+
|
|
50
|
+
## Dependencies
|
|
51
|
+
|
|
52
|
+
**Depends on:** `cjm-context-graph-layer`, `cjm-context-graph-primitives`
|
|
53
|
+
**Used by:** `cjm-context-graph-layer`, `cjm-transcript-correction-core`, `cjm-transcript-correction-tui`, `cjm-transcript-decomp-core`, `cjm-transcription-core`
|
|
@@ -3,11 +3,10 @@ MANIFEST.in
|
|
|
3
3
|
README.md
|
|
4
4
|
pyproject.toml
|
|
5
5
|
cjm_transcript_graph_schema/__init__.py
|
|
6
|
-
cjm_transcript_graph_schema/_modidx.py
|
|
7
6
|
cjm_transcript_graph_schema/schema.py
|
|
8
7
|
cjm_transcript_graph_schema.egg-info/PKG-INFO
|
|
9
8
|
cjm_transcript_graph_schema.egg-info/SOURCES.txt
|
|
10
9
|
cjm_transcript_graph_schema.egg-info/dependency_links.txt
|
|
11
|
-
cjm_transcript_graph_schema.egg-info/entry_points.txt
|
|
12
10
|
cjm_transcript_graph_schema.egg-info/requires.txt
|
|
13
|
-
cjm_transcript_graph_schema.egg-info/top_level.txt
|
|
11
|
+
cjm_transcript_graph_schema.egg-info/top_level.txt
|
|
12
|
+
tests/test_schema.py
|
|
@@ -10,7 +10,7 @@ readme = "README.md"
|
|
|
10
10
|
requires-python = ">=3.12"
|
|
11
11
|
license = {text = "Apache-2.0"}
|
|
12
12
|
authors = [{name = "Christian J. Mills", email = "9126128+cj-mills@users.noreply.github.com"}]
|
|
13
|
-
keywords = ['
|
|
13
|
+
keywords = ['python']
|
|
14
14
|
classifiers = [
|
|
15
15
|
"Programming Language :: Python :: 3",
|
|
16
16
|
"Programming Language :: Python :: 3 :: Only",
|
|
@@ -19,10 +19,6 @@ dependencies = ['cjm-context-graph-layer>=0.0.7', 'cjm-context-graph-primitives>
|
|
|
19
19
|
|
|
20
20
|
[project.urls]
|
|
21
21
|
Repository = "https://github.com/cj-mills/cjm-transcript-graph-schema"
|
|
22
|
-
Documentation = "https://cj-mills.github.io/cjm-transcript-graph-schema/"
|
|
23
|
-
|
|
24
|
-
[project.entry-points.nbdev]
|
|
25
|
-
cjm_transcript_graph_schema = "cjm_transcript_graph_schema._modidx:d"
|
|
26
22
|
|
|
27
23
|
[tool.setuptools.dynamic]
|
|
28
24
|
version = {attr = "cjm_transcript_graph_schema.__version__"}
|
|
@@ -30,4 +26,3 @@ version = {attr = "cjm_transcript_graph_schema.__version__"}
|
|
|
30
26
|
[tool.setuptools.packages.find]
|
|
31
27
|
include = ["cjm_transcript_graph_schema*"]
|
|
32
28
|
|
|
33
|
-
[tool.nbdev]
|