Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions milestones/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ repositories that install `docgen` and maintain their own demo bundle. The
library no longer ships an in-repo dogfood; consumers are the integration test
of record.

**Active:** **[generation-numeric-tunables.md](generation-numeric-tunables.md)** —
narration / scene-generation temperature and context-byte tunables must
be YAML numbers (`true` used to become `1.0` / 1-byte context).
**Active:** **[validation-numeric-tunables.md](validation-numeric-tunables.md)** —
nested validation OCR / layout / av_sync / timing / story_end numerics
must be YAML numbers (`true` used to become `1`).

**Shipped:**
- **[generation-numeric-tunables.md](generation-numeric-tunables.md)** —
narration / scene-generation temperature and context-byte tunables
must be YAML numbers (#117).
- **[hint-segment-create-bool.md](hint-segment-create-bool.md)** —
hint `docgen.segment.create` must be a YAML boolean (#116).
- **[numeric-config-tunables.md](numeric-config-tunables.md)** —
Expand Down
2 changes: 1 addition & 1 deletion milestones/generation-numeric-tunables.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Milestone: LLM generation numeric tunables must be YAML numbers

**Status:** Active
**Status:** Shipped
**PR:** [#117](https://github.com/jmjava/documentation-generator/pull/117)
**Depends on:** `milestones/hint-segment-create-bool.md` (PR #116),
`milestones/numeric-config-tunables.md` (PR #115),
Expand Down
50 changes: 50 additions & 0 deletions milestones/validation-numeric-tunables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Milestone: nested validation numerics must be YAML numbers

**Status:** Active
**PR:** [#118](https://github.com/jmjava/documentation-generator/pull/118)
**Depends on:** `milestones/generation-numeric-tunables.md` (PR #117),
`milestones/numeric-config-tunables.md` (PR #115)

## Problem

Top-level ``validation.max_drift_sec`` / ``max_freeze_ratio`` are
already typed. Nested check tunables still go through ``int()`` /
``float()``. A YAML **bool** is a subclass of ``int``, so:

1. ``validation.av_sync.tolerance_sec: true`` became **1.0s** OCR
slack (vs default 3.0).
2. ``validation.ocr.min_confidence: true`` became **1**.
3. ``validation.story_end.max_early_sec: true`` became **1s**.

A YAML list or string raised ``TypeError`` / ``ValueError`` inside
validate, not ``ConfigError`` at load.

## Goal

Fail closed at ``Config.from_yaml``. Present values of:

- ``validation.ocr.sample_interval_sec`` / ``min_confidence``
- ``validation.layout.min_spacing_px`` / ``edge_margin_px``
- ``validation.av_sync.tolerance_sec`` / ``min_anchors_per_segment`` /
``max_anchors_per_segment``
- ``validation.timing_sync.max_tail_gap_sec`` / ``max_end_overrun_sec``
- ``validation.story_end.max_early_sec`` / ``max_early_ratio``

must be YAML numbers (int or float, not bool). Missing keys keep
defaults.

## Done when

- [x] Present nested tunables must be YAML numbers.
- [x] Tests for bool ``tolerance_sec`` / ``max_early_sec``, list
``min_confidence``, quoted ``max_tail_gap_sec``, and valid
numbers.
- [x] `ruff check src/ tests/`
- [x] `pytest tests/` (682 passed, 1 skipped)
- [x] `docgen benchmark` (no clock change; meets baseline)

## Out of scope

- ``validation.*.enabled`` / ``layout.check_overlap`` /
``prefer_scene_spec_labels`` boolean gates.
- Coercing numeric strings into numbers.
36 changes: 36 additions & 0 deletions src/docgen/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,42 @@ def __post_init__(self) -> None:
label="validation.narration_lint.post_tts_deny_patterns",
source=src,
)
for nkey in ("sample_interval_sec", "min_confidence"):
if ocr.get(nkey) is not None:
require_yaml_number(
ocr[nkey], label=f"validation.ocr.{nkey}", source=src
)
layout = self._sub_block(validation, "layout", label="validation.layout")
for nkey in ("min_spacing_px", "edge_margin_px"):
if layout.get(nkey) is not None:
require_yaml_number(
layout[nkey], label=f"validation.layout.{nkey}", source=src
)
for nkey in (
"tolerance_sec",
"min_anchors_per_segment",
"max_anchors_per_segment",
):
if avs.get(nkey) is not None:
require_yaml_number(
avs[nkey], label=f"validation.av_sync.{nkey}", source=src
)
ts_sync = self._sub_block(
validation, "timing_sync", label="validation.timing_sync"
)
for nkey in ("max_tail_gap_sec", "max_end_overrun_sec"):
if ts_sync.get(nkey) is not None:
require_yaml_number(
ts_sync[nkey],
label=f"validation.timing_sync.{nkey}",
source=src,
)
story = self._sub_block(validation, "story_end", label="validation.story_end")
for nkey in ("max_early_sec", "max_early_ratio"):
if story.get(nkey) is not None:
require_yaml_number(
story[nkey], label=f"validation.story_end.{nkey}", source=src
)

def _source_label(self) -> str:
return self.yaml_path.name if self.yaml_path else "docgen.yaml"
Expand Down
62 changes: 62 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -948,3 +948,65 @@ def test_from_yaml_generation_numeric_tunables_allowed(tmp_path: Path) -> None:
assert c.raw["narration_from_source"]["max_context_bytes"] == 90000
assert c.raw["manim_scene_generation"]["temperature"] == 0.4
assert c.raw["manim_scene_generation"]["max_whisper_words_in_prompt"] == 12


def test_from_yaml_bool_av_sync_tolerance_raises(tmp_path: Path) -> None:
p = tmp_path / "docgen.yaml"
p.write_text("validation:\n av_sync:\n tolerance_sec: true\n", encoding="utf-8")
with pytest.raises(
ConfigError, match="validation.av_sync.tolerance_sec must be a YAML number"
):
Config.from_yaml(p)


def test_from_yaml_list_ocr_min_confidence_raises(tmp_path: Path) -> None:
p = tmp_path / "docgen.yaml"
p.write_text(
"validation:\n ocr:\n min_confidence:\n - 40\n",
encoding="utf-8",
)
with pytest.raises(
ConfigError, match="validation.ocr.min_confidence must be a YAML number"
):
Config.from_yaml(p)


def test_from_yaml_bool_story_end_max_early_sec_raises(tmp_path: Path) -> None:
p = tmp_path / "docgen.yaml"
p.write_text("validation:\n story_end:\n max_early_sec: true\n", encoding="utf-8")
with pytest.raises(
ConfigError, match="validation.story_end.max_early_sec must be a YAML number"
):
Config.from_yaml(p)


def test_from_yaml_string_timing_sync_tail_gap_raises(tmp_path: Path) -> None:
p = tmp_path / "docgen.yaml"
p.write_text(
'validation:\n timing_sync:\n max_tail_gap_sec: "3.0"\n',
encoding="utf-8",
)
with pytest.raises(
ConfigError, match="validation.timing_sync.max_tail_gap_sec must be a YAML number"
):
Config.from_yaml(p)


def test_from_yaml_validation_nested_numerics_allowed(tmp_path: Path) -> None:
p = tmp_path / "docgen.yaml"
p.write_text(
"validation:\n"
" ocr:\n sample_interval_sec: 3\n min_confidence: 50\n"
" layout:\n min_spacing_px: 12\n edge_margin_px: 18\n"
" av_sync:\n tolerance_sec: 2.5\n min_anchors_per_segment: 3\n"
" timing_sync:\n max_tail_gap_sec: 4.0\n max_end_overrun_sec: 1.5\n"
" story_end:\n max_early_sec: 30\n max_early_ratio: 0.4\n",
encoding="utf-8",
)
c = Config.from_yaml(p)
assert c.ocr_config["sample_interval_sec"] == 3
assert c.ocr_config["min_confidence"] == 50
assert c.layout_config["min_spacing_px"] == 12
assert c.av_sync_config["tolerance_sec"] == 2.5
assert c.timing_sync_config["max_tail_gap_sec"] == 4.0
assert c.story_end_config["max_early_sec"] == 30