paces 0.0.17__tar.gz → 0.0.19__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.
- {paces-0.0.17 → paces-0.0.19}/PKG-INFO +1 -1
- {paces-0.0.17 → paces-0.0.19}/paces/ask.py +1 -1
- {paces-0.0.17 → paces-0.0.19}/paces/corrections.py +1 -1
- {paces-0.0.17 → paces-0.0.19}/paces/evidence.py +2 -2
- {paces-0.0.17 → paces-0.0.19}/paces/model.py +10 -2
- {paces-0.0.17 → paces-0.0.19}/paces/projection.py +1 -1
- paces-0.0.19/paces/segmentation.py +74 -0
- {paces-0.0.17 → paces-0.0.19}/paces/segmenters.py +11 -55
- {paces-0.0.17 → paces-0.0.19}/pyproject.toml +1 -1
- {paces-0.0.17 → paces-0.0.19}/tests/test_ask.py +2 -0
- {paces-0.0.17 → paces-0.0.19}/tests/test_segment.py +25 -0
- {paces-0.0.17 → paces-0.0.19}/.gitignore +0 -0
- {paces-0.0.17 → paces-0.0.19}/LICENSE +0 -0
- {paces-0.0.17 → paces-0.0.19}/README.md +0 -0
- {paces-0.0.17 → paces-0.0.19}/docs/README.md +0 -0
- {paces-0.0.17 → paces-0.0.19}/docs/alignment/README.md +0 -0
- {paces-0.0.17 → paces-0.0.19}/docs/poc-reference/README.md +0 -0
- {paces-0.0.17 → paces-0.0.19}/docs/poc-reference/artifacts/clips.json +0 -0
- {paces-0.0.17 → paces-0.0.19}/paces/__init__.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/paces/__main__.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/paces/bodies/__init__.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/paces/bodies/_base.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/paces/bodies/derivation.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/paces/bodies/document.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/paces/bodies/signal.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/paces/bodies/step.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/paces/cues.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/paces/data/ask/ask.css +0 -0
- {paces-0.0.17 → paces-0.0.19}/paces/data/ask/ask.html +0 -0
- {paces-0.0.17 → paces-0.0.19}/paces/data/ask/ask.js +0 -0
- {paces-0.0.17 → paces-0.0.19}/paces/derivation.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/paces/edits.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/paces/excerpts.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/paces/measure.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/paces/pose.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/paces/render.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/paces/timed_text.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/paces/tools.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/tests/audio_synth.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/tests/data/body_schemas.json +0 -0
- {paces-0.0.17 → paces-0.0.19}/tests/data/routine.json +0 -0
- {paces-0.0.17 → paces-0.0.19}/tests/poc_fixture.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/tests/test_chapters.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/tests/test_cli_surface.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/tests/test_corrections.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/tests/test_cues.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/tests/test_derivation.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/tests/test_edit_protection_residuals.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/tests/test_edits.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/tests/test_evidence.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/tests/test_evidence_schemas.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/tests/test_excerpts.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/tests/test_measure.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/tests/test_pose.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/tests/test_render_media.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/tests/test_roundtrip_poc.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/tests/test_smoke.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/tests/test_vertical_slice.py +0 -0
- {paces-0.0.17 → paces-0.0.19}/tests/video_synth.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: paces
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.19
|
|
4
4
|
Summary: Turn instructional media into structured, interactive learning material
|
|
5
5
|
Project-URL: Homepage, https://github.com/thorwhalen/paces
|
|
6
6
|
Project-URL: Repository, https://github.com/thorwhalen/paces
|
|
@@ -49,7 +49,7 @@ from paces.corrections import (
|
|
|
49
49
|
time_str,
|
|
50
50
|
)
|
|
51
51
|
from paces.model import seconds_per_unit
|
|
52
|
-
from paces.
|
|
52
|
+
from paces.segmentation import Segmentation
|
|
53
53
|
|
|
54
54
|
PROPOSAL_KIND = "PacesAskProposal"
|
|
55
55
|
ANSWERS_KIND = "PacesAskAnswers"
|
|
@@ -144,7 +144,7 @@ from paces.model import (
|
|
|
144
144
|
StepDocument,
|
|
145
145
|
)
|
|
146
146
|
from paces.projection import to_document
|
|
147
|
-
from paces.
|
|
147
|
+
from paces.segmentation import Segmentation
|
|
148
148
|
|
|
149
149
|
#: The namespace every paces annotation id is derived under. Derived from the
|
|
150
150
|
#: repository URL rather than typed as a literal, so it is reproducible from
|
|
@@ -454,7 +454,7 @@ def to_store(
|
|
|
454
454
|
*seg* is the analysis result; the projection arguments (*doc_id*,
|
|
455
455
|
*title*, *source*, *domain*, *lang*) are :func:`paces.to_document`'s,
|
|
456
456
|
unchanged. Everything else is evidence a segmenter does not carry on
|
|
457
|
-
:class:`~paces.
|
|
457
|
+
:class:`~paces.segmentation.Segmentation` but a pipeline may have:
|
|
458
458
|
|
|
459
459
|
Args:
|
|
460
460
|
store: Any ``lacing.IntervalAnnotationStore`` — ``MemoryStore()`` for
|
|
@@ -24,6 +24,7 @@ Design decisions, each argued in ``docs/07-annotation-model.md``:
|
|
|
24
24
|
|
|
25
25
|
from __future__ import annotations
|
|
26
26
|
|
|
27
|
+
import math
|
|
27
28
|
from collections.abc import Mapping
|
|
28
29
|
from datetime import datetime
|
|
29
30
|
from fractions import Fraction
|
|
@@ -286,9 +287,16 @@ def decimal_str(value: float, *, places: int = 3) -> str:
|
|
|
286
287
|
|
|
287
288
|
>>> decimal_str(72.60000000000001, places=3)
|
|
288
289
|
'72.6'
|
|
290
|
+
>>> decimal_str(-0.0004, places=3) # rounds to zero: no sign
|
|
291
|
+
'0'
|
|
289
292
|
"""
|
|
290
|
-
|
|
291
|
-
|
|
293
|
+
number = float(value)
|
|
294
|
+
if not math.isfinite(number):
|
|
295
|
+
raise ValueError(f"no decimal string for a non-finite value: {value!r}")
|
|
296
|
+
text = f"{number:.{places}f}"
|
|
297
|
+
if "." in text: # places=0 has no fraction to trim
|
|
298
|
+
text = text.rstrip("0").rstrip(".")
|
|
299
|
+
return "0" if text in ("", "-0") else text
|
|
292
300
|
|
|
293
301
|
|
|
294
302
|
# ── serialisation (the git rules of docs/07 §6.5) ───────────────────────────
|
|
@@ -21,7 +21,7 @@ from paces.model import (
|
|
|
21
21
|
StepDocument,
|
|
22
22
|
seconds_per_unit,
|
|
23
23
|
)
|
|
24
|
-
from paces.
|
|
24
|
+
from paces.segmentation import SegStep, Segmentation
|
|
25
25
|
|
|
26
26
|
#: Decimal places kept when converting computed seconds to wire decimals —
|
|
27
27
|
#: matches the POC's 0.1 s working resolution with headroom.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""What a segmenter returns: :class:`Segmentation`, its :class:`SegStep` records and
|
|
2
|
+
the ``Span`` they cover — the analysis-result types, with nothing else.
|
|
3
|
+
|
|
4
|
+
They live apart from :mod:`paces.segmenters` (the registry and the
|
|
5
|
+
``segment()`` facade) so a module that only consumes or reshapes a result —
|
|
6
|
+
:mod:`paces.corrections`, :mod:`paces.projection`, :mod:`paces.ask`, the
|
|
7
|
+
evidence layer — depends on the types and not on the facade, and the facade
|
|
8
|
+
can import those modules without a cycle. :mod:`paces.segmenters` re-exports
|
|
9
|
+
every name here, so ``from paces.segmenters import Segmentation`` keeps
|
|
10
|
+
working.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from collections.abc import Mapping
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from paces.model import MetricGrid
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"DFLT_CHAPTERS_CONFIDENCE",
|
|
23
|
+
"DFLT_EXPLICIT_CONFIDENCE",
|
|
24
|
+
"DFLT_GRID_CONFIDENCE",
|
|
25
|
+
"SegStep",
|
|
26
|
+
"Segmentation",
|
|
27
|
+
"Span",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
Span = tuple[float, float] # seconds, half-open
|
|
31
|
+
|
|
32
|
+
# Confidence constants: placement from user-supplied inputs is arithmetic, so
|
|
33
|
+
# what these grade is trust in the inputs, not in any inference.
|
|
34
|
+
DFLT_EXPLICIT_CONFIDENCE = 0.95 # a human said so
|
|
35
|
+
DFLT_GRID_CONFIDENCE = 0.9 # exact given the grid; the grid itself may drift
|
|
36
|
+
DFLT_CHAPTERS_CONFIDENCE = 0.9 # the author said so, in metadata
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
40
|
+
class SegStep:
|
|
41
|
+
"""One named piece of the media, as an analysis result.
|
|
42
|
+
|
|
43
|
+
``name=''`` means "found, not named" — a VALID state, not an error.
|
|
44
|
+
``spans`` is a tuple because a step legitimately appears in several places
|
|
45
|
+
(run-through AND breakdown).
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
id: str
|
|
49
|
+
name: str = ""
|
|
50
|
+
spans: tuple[Span, ...] = ()
|
|
51
|
+
confidence: float = 0.0
|
|
52
|
+
children: tuple["SegStep", ...] = ()
|
|
53
|
+
source: str = "" # which capability produced it
|
|
54
|
+
evidence: Mapping[str, Any] = field(default_factory=dict)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
58
|
+
class Segmentation:
|
|
59
|
+
"""What every segmenter returns. Reviewable, re-runnable, arguable.
|
|
60
|
+
|
|
61
|
+
``boundaries`` is separate from ``steps`` and populated even when naming
|
|
62
|
+
failed: nine excellent unnamed cuts are a useful, honest result. ``steps``
|
|
63
|
+
may be empty while ``boundaries`` is not.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
steps: tuple[SegStep, ...] = () # ordered; MAY be empty while boundaries is not
|
|
67
|
+
boundaries: tuple[float, ...] = () # the raw cut set, always
|
|
68
|
+
unit: str = "seconds" # "eight" | "rep" | "seconds"
|
|
69
|
+
grid: MetricGrid | None = None
|
|
70
|
+
confidence: float = 0.0
|
|
71
|
+
method: str = "" # which capability ran (the ADR's "reported default")
|
|
72
|
+
flags: tuple[str, ...] = () # 'naming-abstained', 'no-signal', 'try: ...'
|
|
73
|
+
elapsed_s: float = 0.0
|
|
74
|
+
spent_usd: float = 0.0
|
|
@@ -44,55 +44,18 @@ from dataclasses import dataclass, field
|
|
|
44
44
|
from pathlib import Path
|
|
45
45
|
from typing import Any, Callable
|
|
46
46
|
|
|
47
|
+
from paces import corrections as corrections_module
|
|
47
48
|
from paces.model import MetricGrid, seconds_per_unit
|
|
49
|
+
from paces.segmentation import ( # noqa: F401 — re-exported; their home is there
|
|
50
|
+
DFLT_CHAPTERS_CONFIDENCE,
|
|
51
|
+
DFLT_EXPLICIT_CONFIDENCE,
|
|
52
|
+
DFLT_GRID_CONFIDENCE,
|
|
53
|
+
SegStep,
|
|
54
|
+
Segmentation,
|
|
55
|
+
Span,
|
|
56
|
+
)
|
|
48
57
|
from paces.timed_text import normalize_timed_text
|
|
49
58
|
|
|
50
|
-
Span = tuple[float, float] # seconds, half-open
|
|
51
|
-
|
|
52
|
-
# Confidence constants: placement from user-supplied inputs is arithmetic, so
|
|
53
|
-
# what these grade is trust in the inputs, not in any inference.
|
|
54
|
-
DFLT_EXPLICIT_CONFIDENCE = 0.95 # a human said so
|
|
55
|
-
DFLT_GRID_CONFIDENCE = 0.9 # exact given the grid; the grid itself may drift
|
|
56
|
-
DFLT_CHAPTERS_CONFIDENCE = 0.9 # the author said so, in metadata
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
60
|
-
class SegStep:
|
|
61
|
-
"""One named piece of the media, as an analysis result.
|
|
62
|
-
|
|
63
|
-
``name=''`` means "found, not named" — a VALID state, not an error.
|
|
64
|
-
``spans`` is a tuple because a step legitimately appears in several places
|
|
65
|
-
(run-through AND breakdown).
|
|
66
|
-
"""
|
|
67
|
-
|
|
68
|
-
id: str
|
|
69
|
-
name: str = ""
|
|
70
|
-
spans: tuple[Span, ...] = ()
|
|
71
|
-
confidence: float = 0.0
|
|
72
|
-
children: tuple["SegStep", ...] = ()
|
|
73
|
-
source: str = "" # which capability produced it
|
|
74
|
-
evidence: Mapping[str, Any] = field(default_factory=dict)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
78
|
-
class Segmentation:
|
|
79
|
-
"""What every segmenter returns. Reviewable, re-runnable, arguable.
|
|
80
|
-
|
|
81
|
-
``boundaries`` is separate from ``steps`` and populated even when naming
|
|
82
|
-
failed: nine excellent unnamed cuts are a useful, honest result. ``steps``
|
|
83
|
-
may be empty while ``boundaries`` is not.
|
|
84
|
-
"""
|
|
85
|
-
|
|
86
|
-
steps: tuple[SegStep, ...] = () # ordered; MAY be empty while boundaries is not
|
|
87
|
-
boundaries: tuple[float, ...] = () # the raw cut set, always
|
|
88
|
-
unit: str = "seconds" # "eight" | "rep" | "seconds"
|
|
89
|
-
grid: MetricGrid | None = None
|
|
90
|
-
confidence: float = 0.0
|
|
91
|
-
method: str = "" # which capability ran (the ADR's "reported default")
|
|
92
|
-
flags: tuple[str, ...] = () # 'naming-abstained', 'no-signal', 'try: ...'
|
|
93
|
-
elapsed_s: float = 0.0
|
|
94
|
-
spent_usd: float = 0.0
|
|
95
|
-
|
|
96
59
|
|
|
97
60
|
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
98
61
|
class Capability:
|
|
@@ -354,13 +317,6 @@ def _facts(
|
|
|
354
317
|
return frozenset(facts)
|
|
355
318
|
|
|
356
319
|
|
|
357
|
-
def _corrections_api():
|
|
358
|
-
""":mod:`paces.corrections`, imported lazily: it imports this module."""
|
|
359
|
-
from paces import corrections
|
|
360
|
-
|
|
361
|
-
return corrections
|
|
362
|
-
|
|
363
|
-
|
|
364
320
|
def _apply_corrections(
|
|
365
321
|
result: Segmentation, log: tuple, capability: Capability | None
|
|
366
322
|
) -> Segmentation:
|
|
@@ -370,7 +326,7 @@ def _apply_corrections(
|
|
|
370
326
|
unmatched — rather than dropped."""
|
|
371
327
|
if not log or (capability is not None and "corrections" in capability.reads):
|
|
372
328
|
return result
|
|
373
|
-
return
|
|
329
|
+
return corrections_module.replay(result, log)
|
|
374
330
|
|
|
375
331
|
|
|
376
332
|
# ── the facade ──────────────────────────────────────────────────────────────
|
|
@@ -418,7 +374,7 @@ def segment(
|
|
|
418
374
|
transcript_cues = normalize_timed_text(transcript)
|
|
419
375
|
# fail fast: a malformed log raises before any segmenter runs
|
|
420
376
|
correction_log = (
|
|
421
|
-
|
|
377
|
+
corrections_module.normalize_corrections(corrections)
|
|
422
378
|
if corrections is not None
|
|
423
379
|
else ()
|
|
424
380
|
)
|
|
@@ -232,6 +232,8 @@ def test_js_time_format_matches_the_python_wire_format():
|
|
|
232
232
|
values = [0, 15, 95.8, 0.8550444, 10.1235, 1e-7, 123.4567, 7.0005, 59.9996]
|
|
233
233
|
# exact binary ties: Python's format rounds half-even, toFixed rounds up
|
|
234
234
|
values += [10.0625, 0.0625, 0.1875, 0.3125, 2.5625, 1023.9375, 0.0005]
|
|
235
|
+
# values that round to zero carry no sign, in either language (#40)
|
|
236
|
+
values += [-0.0004, -0.0, -1e-9]
|
|
235
237
|
got = _run_js(f"{json.dumps(values)}.map(core.timeStr)")
|
|
236
238
|
assert got == [decimal_str(v, places=3) for v in values]
|
|
237
239
|
|
|
@@ -145,3 +145,28 @@ def test_tools_segment_accepts_json_strings():
|
|
|
145
145
|
assert payload["method"] == "grid-placed"
|
|
146
146
|
assert payload["steps"][0]["name"] == "intro"
|
|
147
147
|
json.dumps(payload) # JSON-ready throughout
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@pytest.mark.parametrize(
|
|
151
|
+
"value, places, expected",
|
|
152
|
+
[
|
|
153
|
+
(-0.0004, 3, "0"), # rounds to zero: no sign (#40)
|
|
154
|
+
(-0.0, 3, "0"),
|
|
155
|
+
(-0.0005, 3, "-0.001"),
|
|
156
|
+
(100, 0, "100"), # places=0: no fraction to trim, digits stay
|
|
157
|
+
(-0.4, 0, "0"),
|
|
158
|
+
(72.60000000000001, 3, "72.6"),
|
|
159
|
+
],
|
|
160
|
+
)
|
|
161
|
+
def test_decimal_str_writes_clean_wire_decimals(value, places, expected):
|
|
162
|
+
from paces.model import decimal_str
|
|
163
|
+
|
|
164
|
+
assert decimal_str(value, places=places) == expected
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@pytest.mark.parametrize("value", [float("nan"), float("inf"), float("-inf")])
|
|
168
|
+
def test_decimal_str_refuses_non_finite_values(value):
|
|
169
|
+
from paces.model import decimal_str
|
|
170
|
+
|
|
171
|
+
with pytest.raises(ValueError, match="non-finite"):
|
|
172
|
+
decimal_str(value)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|