utterplan 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,22 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+ from typing import Any
5
+
6
+ from .model import UtterancePlan
7
+
8
+
9
+ def to_dict(plan: UtterancePlan) -> dict[str, Any]:
10
+ return plan.to_dict()
11
+
12
+
13
+ def from_dict(data: Mapping[str, Any]) -> UtterancePlan:
14
+ return UtterancePlan.from_dict(data)
15
+
16
+
17
+ def to_json(plan: UtterancePlan, *, indent: int | None = 2) -> str:
18
+ return plan.to_json(indent=indent)
19
+
20
+
21
+ def from_json(value: str) -> UtterancePlan:
22
+ return UtterancePlan.from_json(value)
@@ -0,0 +1,21 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal
4
+
5
+ SpacyModelSize = Literal["sm", "md", "lg", "trf"]
6
+
7
+
8
+ def normalize_spacy_language(language: str | None) -> str:
9
+ value = (language or "en").strip().lower().replace("_", "-")
10
+ return value.split("-", 1)[0] or "en"
11
+
12
+
13
+ def resolve_spacy_model(language: str, size: SpacyModelSize = "sm") -> str:
14
+ if size not in {"sm", "md", "lg", "trf"}:
15
+ raise ValueError("spacy model size must be one of sm, md, lg, trf")
16
+ base = normalize_spacy_language(language)
17
+ family = "web" if base in {"en", "zh"} else "news"
18
+ return f"{base}_core_{family}_{size}"
19
+
20
+
21
+ __all__ = ["SpacyModelSize", "normalize_spacy_language", "resolve_spacy_model"]
utterplan/spans.py ADDED
@@ -0,0 +1,3 @@
1
+ from .model import AnnotationSpan, BoundaryEvent
2
+
3
+ __all__ = ["AnnotationSpan", "BoundaryEvent"]
File without changes
File without changes
@@ -0,0 +1,4 @@
1
+ from ...parsers import PlainDocumentParser
2
+ from .. import protocols
3
+
4
+ __all__ = ["PlainDocumentParser", "protocols"]
@@ -0,0 +1,3 @@
1
+ from ...parsers import SSMDDocumentParser
2
+
3
+ __all__ = ["SSMDDocumentParser"]
File without changes
@@ -0,0 +1,3 @@
1
+ from ...preparation import IdentityTextPreparer
2
+
3
+ __all__ = ["IdentityTextPreparer"]
@@ -0,0 +1,3 @@
1
+ from ...preparation import SpokenformTextPreparer
2
+
3
+ __all__ = ["SpokenformTextPreparer"]
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Protocol
4
+
5
+ from ..config import PlannerConfig
6
+ from ..model import AnnotationSpan, BoundaryEvent
7
+ from ..parsers import ParsedDocument
8
+
9
+
10
+ class DocumentParser(Protocol):
11
+ def parse(self, text: str, config: PlannerConfig) -> ParsedDocument: ...
12
+
13
+
14
+ class TextPreparer(Protocol):
15
+ def prepare(
16
+ self,
17
+ text: str,
18
+ language: str,
19
+ runs: tuple[object, ...],
20
+ annotations: tuple[AnnotationSpan, ...] = (),
21
+ boundaries: tuple[BoundaryEvent, ...] = (),
22
+ ) -> object: ...
23
+
24
+
25
+ __all__ = ["DocumentParser", "TextPreparer"]
File without changes
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ from ...config import PlannerConfig
4
+ from ...model import PlanSegment
5
+
6
+
7
+ def split_text(text: str, config: PlannerConfig) -> tuple[PlanSegment, ...]:
8
+ try:
9
+ import phrasplit
10
+
11
+ items = phrasplit.split_with_offsets(
12
+ text, mode="sentence", use_spacy=False, language=config.language
13
+ )
14
+ except (ImportError, OSError, TypeError, ValueError):
15
+ items = ()
16
+ result = []
17
+ for index, item in enumerate(items):
18
+ start, end = int(item.char_start), int(item.char_end)
19
+ result.append(
20
+ PlanSegment(
21
+ f"seg-{index:06d}",
22
+ text[start:end],
23
+ start,
24
+ end,
25
+ config.language,
26
+ int(getattr(item, "paragraph_idx", 0) or 0),
27
+ int(getattr(item, "sentence_idx", 0) or 0),
28
+ )
29
+ )
30
+ if not result and text:
31
+ result.append(PlanSegment("seg-000000", text, 0, len(text), config.language))
32
+ return tuple(result)
33
+
34
+
35
+ __all__ = ["split_text"]
utterplan/units.py ADDED
@@ -0,0 +1,81 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import replace
4
+
5
+ from .hashing import semantic_hash, unit_hash_payload
6
+ from .model import Marker, PlanSegment, PlanUnit
7
+
8
+
9
+ def make_units(
10
+ segments: tuple[PlanSegment, ...], markers: tuple[Marker, ...], kind: str
11
+ ) -> tuple[PlanUnit, ...]:
12
+ if not segments:
13
+ return ()
14
+ groups: list[list[PlanSegment]] = []
15
+ current: list[PlanSegment] = []
16
+ current_key: tuple[int, int] | None = None
17
+ closed: set[tuple[int, int]] = set()
18
+ for segment in segments:
19
+ key = (segment.paragraph, segment.sentence if kind == "sentence" else -1)
20
+ if current and key != current_key:
21
+ groups.append(current)
22
+ if current_key is not None:
23
+ closed.add(current_key)
24
+ current = []
25
+ if key in closed:
26
+ raise ValueError(f"non-contiguous unit key {key}")
27
+ current.append(segment)
28
+ current_key = key
29
+ if current:
30
+ groups.append(current)
31
+
32
+ units: list[PlanUnit] = []
33
+ for index, group in enumerate(groups):
34
+ start, end = group[0].spoken_start, group[-1].spoken_end
35
+ marker_ids = tuple(
36
+ marker.id
37
+ for marker in markers
38
+ if _marker_belongs(marker.spoken_position, groups, index)
39
+ )
40
+ marker_values = tuple(marker for marker in markers if marker.id in marker_ids)
41
+ provisional = PlanUnit(
42
+ f"unit-{index:04d}",
43
+ index,
44
+ kind,
45
+ start,
46
+ end,
47
+ tuple(segment.id for segment in group),
48
+ marker_ids,
49
+ )
50
+ units.append(
51
+ replace(
52
+ provisional,
53
+ content_hash=semantic_hash(
54
+ unit_hash_payload(_UnitView(group, marker_ids, marker_values))
55
+ ),
56
+ )
57
+ )
58
+ return tuple(units)
59
+
60
+
61
+ def _marker_belongs(position: int, groups: list[list[PlanSegment]], index: int) -> bool:
62
+ group = groups[index]
63
+ start = group[0].spoken_start
64
+ end = group[-1].spoken_end
65
+ if index < len(groups) - 1 and end <= position <= groups[index + 1][0].spoken_start:
66
+ return False
67
+ if index > 0 and groups[index - 1][-1].spoken_end <= position <= start:
68
+ return True
69
+ return start <= position < end or (index == len(groups) - 1 and position == end)
70
+
71
+
72
+ class _UnitView:
73
+ def __init__(
74
+ self,
75
+ segments: list[PlanSegment],
76
+ marker_ids: tuple[str, ...],
77
+ marker_values: tuple[Marker, ...],
78
+ ) -> None:
79
+ self.segments = tuple(segments)
80
+ self.marker_ids = marker_ids
81
+ self.marker_values = marker_values
@@ -0,0 +1,260 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/buchwandler/utterplan/blob/main/spec/utterplan.schema.json",
4
+ "title": "UtterPlan schema",
5
+ "type": "object",
6
+ "required": [
7
+ "format",
8
+ "schema_version",
9
+ "producer",
10
+ "plan_id",
11
+ "source",
12
+ "config",
13
+ "texts",
14
+ "preparation",
15
+ "languages",
16
+ "annotations",
17
+ "boundaries",
18
+ "tokens",
19
+ "segments",
20
+ "units",
21
+ "markers",
22
+ "warnings",
23
+ "diagnostics"
24
+ ],
25
+ "properties": {
26
+ "format": { "const": "utterplan" },
27
+ "schema_version": { "const": 1 },
28
+ "producer": {
29
+ "type": "object",
30
+ "required": ["name", "version"],
31
+ "properties": {
32
+ "name": { "type": "string" },
33
+ "version": { "type": "string" }
34
+ },
35
+ "additionalProperties": true
36
+ },
37
+ "plan_id": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" },
38
+ "source": { "$ref": "#/$defs/source" },
39
+ "config": { "type": "object" },
40
+ "texts": {
41
+ "type": "object",
42
+ "required": ["structural", "spoken"],
43
+ "properties": {
44
+ "structural": { "type": "string" },
45
+ "spoken": { "type": "string" }
46
+ },
47
+ "additionalProperties": false
48
+ },
49
+ "preparation": { "$ref": "#/$defs/preparation" },
50
+ "languages": { "type": "array", "items": { "$ref": "#/$defs/language" } },
51
+ "annotations": {
52
+ "type": "array",
53
+ "items": { "$ref": "#/$defs/annotation" }
54
+ },
55
+ "boundaries": { "type": "array", "items": { "$ref": "#/$defs/boundary" } },
56
+ "tokens": { "type": "array", "items": { "$ref": "#/$defs/token" } },
57
+ "segments": { "type": "array", "items": { "$ref": "#/$defs/segment" } },
58
+ "units": { "type": "array", "items": { "$ref": "#/$defs/unit" } },
59
+ "markers": { "type": "array", "items": { "$ref": "#/$defs/marker" } },
60
+ "document_metadata": { "type": "object" },
61
+ "warnings": { "type": "array", "items": { "type": "string" } },
62
+ "diagnostics": {
63
+ "type": "array",
64
+ "items": { "$ref": "#/$defs/diagnostic" }
65
+ }
66
+ },
67
+ "additionalProperties": false,
68
+ "$defs": {
69
+ "source": {
70
+ "type": "object",
71
+ "required": ["format", "text"],
72
+ "properties": {
73
+ "format": { "enum": ["plain", "ssmd"] },
74
+ "text": { "type": "string" }
75
+ },
76
+ "additionalProperties": false
77
+ },
78
+ "preparation": {
79
+ "type": "object",
80
+ "required": ["backend", "languages", "replacements", "warnings"],
81
+ "properties": {
82
+ "backend": { "type": "string" },
83
+ "version": { "type": ["string", "null"] },
84
+ "languages": { "type": "array", "items": { "type": "string" } },
85
+ "replacements": { "type": "array", "items": { "type": "object" } },
86
+ "warnings": { "type": "array", "items": { "type": "string" } }
87
+ },
88
+ "additionalProperties": false
89
+ },
90
+ "range": { "type": "integer", "minimum": 0 },
91
+ "language": {
92
+ "type": "object",
93
+ "required": ["id", "spoken_start", "spoken_end", "language", "source"],
94
+ "properties": {
95
+ "id": { "type": "string" },
96
+ "spoken_start": { "$ref": "#/$defs/range" },
97
+ "spoken_end": { "$ref": "#/$defs/range" },
98
+ "language": { "type": "string" },
99
+ "source": { "type": "string" }
100
+ },
101
+ "additionalProperties": false
102
+ },
103
+ "annotation": {
104
+ "type": "object",
105
+ "required": [
106
+ "id",
107
+ "structural_start",
108
+ "structural_end",
109
+ "spoken_start",
110
+ "spoken_end",
111
+ "kind",
112
+ "attrs"
113
+ ],
114
+ "properties": {
115
+ "id": { "type": "string" },
116
+ "structural_start": { "$ref": "#/$defs/range" },
117
+ "structural_end": { "$ref": "#/$defs/range" },
118
+ "spoken_start": { "type": ["integer", "null"], "minimum": 0 },
119
+ "spoken_end": { "type": ["integer", "null"], "minimum": 0 },
120
+ "kind": { "type": "string" },
121
+ "attrs": { "type": "object" }
122
+ },
123
+ "additionalProperties": false
124
+ },
125
+ "boundary": {
126
+ "type": "object",
127
+ "required": ["id", "position", "kind", "seconds", "origin", "strength"],
128
+ "properties": {
129
+ "id": { "type": "string" },
130
+ "position": { "$ref": "#/$defs/range" },
131
+ "kind": { "type": "string" },
132
+ "seconds": { "type": ["number", "null"], "minimum": 0 },
133
+ "origin": { "type": "string" },
134
+ "strength": { "type": ["string", "null"] },
135
+ "attrs": { "type": "object" }
136
+ },
137
+ "additionalProperties": false
138
+ },
139
+ "token": {
140
+ "type": "object",
141
+ "required": ["spoken_start", "spoken_end", "text"],
142
+ "properties": {
143
+ "id": { "type": "string" },
144
+ "spoken_start": { "$ref": "#/$defs/range" },
145
+ "spoken_end": { "$ref": "#/$defs/range" },
146
+ "text": { "type": "string" },
147
+ "pos": { "type": ["string", "null"] },
148
+ "tag": { "type": ["string", "null"] },
149
+ "lemma": { "type": ["string", "null"] },
150
+ "language": { "type": ["string", "null"] }
151
+ },
152
+ "additionalProperties": false
153
+ },
154
+ "pause": {
155
+ "type": "object",
156
+ "required": ["seconds", "events"],
157
+ "properties": {
158
+ "seconds": { "type": "number", "minimum": 0 },
159
+ "events": { "type": "array", "items": { "type": "string" } }
160
+ },
161
+ "additionalProperties": false
162
+ },
163
+ "directives": {
164
+ "type": "object",
165
+ "properties": {
166
+ "voice": { "type": ["object", "null"] },
167
+ "pronunciation": { "type": ["object", "null"] },
168
+ "prosody": { "type": ["object", "null"] },
169
+ "emphasis": { "type": ["object", "null"] },
170
+ "audio": { "type": ["object", "null"] }
171
+ },
172
+ "additionalProperties": false
173
+ },
174
+ "segment": {
175
+ "type": "object",
176
+ "required": [
177
+ "id",
178
+ "text",
179
+ "spoken_start",
180
+ "spoken_end",
181
+ "language",
182
+ "paragraph",
183
+ "sentence",
184
+ "clause",
185
+ "pause_before",
186
+ "pause_after",
187
+ "directives",
188
+ "token_indices",
189
+ "annotation_ids"
190
+ ],
191
+ "properties": {
192
+ "id": { "type": "string" },
193
+ "text": { "type": "string" },
194
+ "spoken_start": { "$ref": "#/$defs/range" },
195
+ "spoken_end": { "$ref": "#/$defs/range" },
196
+ "language": { "type": "string" },
197
+ "paragraph": { "type": "integer", "minimum": 0 },
198
+ "sentence": { "type": "integer", "minimum": 0 },
199
+ "clause": { "type": "integer", "minimum": 0 },
200
+ "structural_start": { "type": ["integer", "null"], "minimum": 0 },
201
+ "structural_end": { "type": ["integer", "null"], "minimum": 0 },
202
+ "pause_before": { "$ref": "#/$defs/pause" },
203
+ "pause_after": { "$ref": "#/$defs/pause" },
204
+ "directives": { "$ref": "#/$defs/directives" },
205
+ "token_indices": {
206
+ "type": "array",
207
+ "items": { "type": "integer", "minimum": 0 }
208
+ },
209
+ "annotation_ids": { "type": "array", "items": { "type": "string" } }
210
+ },
211
+ "additionalProperties": false
212
+ },
213
+ "unit": {
214
+ "type": "object",
215
+ "required": [
216
+ "id",
217
+ "index",
218
+ "kind",
219
+ "spoken_start",
220
+ "spoken_end",
221
+ "segment_ids",
222
+ "marker_ids",
223
+ "content_hash"
224
+ ],
225
+ "properties": {
226
+ "id": { "type": "string" },
227
+ "index": { "type": "integer", "minimum": 0 },
228
+ "kind": { "enum": ["paragraph", "sentence"] },
229
+ "spoken_start": { "$ref": "#/$defs/range" },
230
+ "spoken_end": { "$ref": "#/$defs/range" },
231
+ "segment_ids": { "type": "array", "items": { "type": "string" } },
232
+ "marker_ids": { "type": "array", "items": { "type": "string" } },
233
+ "content_hash": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" }
234
+ },
235
+ "additionalProperties": false
236
+ },
237
+ "marker": {
238
+ "type": "object",
239
+ "required": ["id", "name", "spoken_position"],
240
+ "properties": {
241
+ "id": { "type": "string" },
242
+ "name": { "type": "string" },
243
+ "spoken_position": { "$ref": "#/$defs/range" },
244
+ "attrs": { "type": "object" }
245
+ },
246
+ "additionalProperties": false
247
+ },
248
+ "diagnostic": {
249
+ "type": "object",
250
+ "required": ["code", "message", "severity", "path"],
251
+ "properties": {
252
+ "code": { "type": "string" },
253
+ "message": { "type": "string" },
254
+ "severity": { "type": "string" },
255
+ "path": { "type": ["string", "null"] }
256
+ },
257
+ "additionalProperties": false
258
+ }
259
+ }
260
+ }