Initial version of application

This commit is contained in:
2026-04-21 10:00:01 -05:00
parent fd0f57274d
commit e10349302f
22 changed files with 1250 additions and 172 deletions

40
tests/test_chunking.py Normal file
View File

@@ -0,0 +1,40 @@
import pytest
from audita.chunking import chunk_transcript
from audita.errors import AuditaValidationError
from audita.schemas import parse_transcript_json
class CountEstimator:
def estimate_json(self, value):
return len(value) * 10
def _segments(count):
payload = [
{"speaker": "Eric", "start": float(i), "end": float(i + 1), "text": f"Segment {i}"}
for i in range(count)
]
import json
return parse_transcript_json(json.dumps(payload))
def test_chunk_transcript_splits_on_segment_boundaries():
sections = chunk_transcript(_segments(5), max_section_tokens=20, estimator=CountEstimator())
assert [len(section.segments) for section in sections] == [2, 2, 1]
assert [section.start_index for section in sections] == [0, 2, 4]
def test_chunk_transcript_allows_exact_limit():
sections = chunk_transcript(_segments(2), max_section_tokens=20, estimator=CountEstimator())
assert len(sections) == 1
assert len(sections[0].segments) == 2
def test_chunk_transcript_rejects_oversized_single_segment():
with pytest.raises(AuditaValidationError):
chunk_transcript(_segments(1), max_section_tokens=9, estimator=CountEstimator())

65
tests/test_config.py Normal file
View File

@@ -0,0 +1,65 @@
from pathlib import Path
import pytest
from audita.config import AuditaConfig, ConfigOverrides
from audita.config import DEFAULT_MAX_RETRIES, DEFAULT_MAX_SECTION_TOKENS, DEFAULT_WORK_DIR
from audita.errors import AuditaConfigError
def test_config_uses_defaults_with_api_key():
config = AuditaConfig.from_sources(env={"OPENROUTER_API_KEY": "key"})
assert config.max_section_tokens == DEFAULT_MAX_SECTION_TOKENS
assert config.max_retries == DEFAULT_MAX_RETRIES
assert config.work_dir == Path(DEFAULT_WORK_DIR)
def test_config_env_overrides_defaults():
config = AuditaConfig.from_sources(
env={
"OPENROUTER_API_KEY": "key",
"AUDITA_MAX_SECTION_TOKENS": "42",
"AUDITA_CONFIDENCE_THRESHOLD": "0.9",
"AUDITA_MAX_RETRIES": "5",
"AUDITA_WORK_DIR": "/tmp/custom-audita",
}
)
assert config.max_section_tokens == 42
assert config.confidence_threshold == 0.9
assert config.max_retries == 5
assert config.work_dir == Path("/tmp/custom-audita")
def test_config_cli_overrides_env():
config = AuditaConfig.from_sources(
env={
"OPENROUTER_API_KEY": "key",
"AUDITA_MAX_SECTION_TOKENS": "42",
"AUDITA_MAX_RETRIES": "5",
"AUDITA_WORK_DIR": "/tmp/env-audita",
},
overrides=ConfigOverrides(
max_section_tokens=100,
max_retries=3,
work_dir=Path("/tmp/cli-audita"),
),
)
assert config.max_section_tokens == 100
assert config.max_retries == 3
assert config.work_dir == Path("/tmp/cli-audita")
def test_config_requires_api_key():
with pytest.raises(AuditaConfigError):
AuditaConfig.from_sources(env={})
def test_config_rejects_bad_env_int():
with pytest.raises(AuditaConfigError):
AuditaConfig.from_sources(
env={"OPENROUTER_API_KEY": "key", "AUDITA_MAX_SECTION_TOKENS": "many"}
)

88
tests/test_corrections.py Normal file
View File

@@ -0,0 +1,88 @@
import pytest
from audita.corrections import apply_corrections
from audita.errors import AuditaValidationError
from audita.schemas import CorrectionCandidate, parse_transcript_json
def _transcript():
return parse_transcript_json(
"""
[
{"speaker": "Eric", "start": 10.0, "end": 11.0, "text": "I ask Chontia."},
{"speaker": "Mike", "start": 0.0, "end": 1.0, "text": "Then Lyra."}
]
"""
)
def test_apply_corrections_uses_threshold_and_sorts_chronologically():
transcript = _transcript()
corrections = [
CorrectionCandidate(
segment_index=0,
speaker="Eric",
start=10.0,
end=11.0,
original_text="I ask Chontia.",
corrected_text="I ask Chauntea.",
confidence=0.8,
)
]
revised = apply_corrections(transcript, corrections, confidence_threshold=0.8)
assert [segment.speaker for segment in revised] == ["Mike", "Eric"]
assert revised[1].text == "I ask Chauntea."
def test_apply_corrections_ignores_below_threshold():
transcript = _transcript()
corrections = [
CorrectionCandidate(
segment_index=0,
speaker="Eric",
start=10.0,
end=11.0,
original_text="I ask Chontia.",
corrected_text="I ask Chauntea.",
confidence=0.79,
)
]
revised = apply_corrections(transcript, corrections, confidence_threshold=0.8)
assert revised[1].text == "I ask Chontia."
def test_apply_corrections_rejects_duplicate_targets():
transcript = _transcript()
correction = CorrectionCandidate(
segment_index=0,
speaker="Eric",
start=10.0,
end=11.0,
original_text="I ask Chontia.",
corrected_text="I ask Chauntea.",
confidence=0.8,
)
with pytest.raises(AuditaValidationError):
apply_corrections(transcript, [correction, correction], confidence_threshold=0.8)
def test_apply_corrections_rejects_mismatched_original_text():
transcript = _transcript()
correction = CorrectionCandidate(
segment_index=0,
speaker="Eric",
start=10.0,
end=11.0,
original_text="Different text.",
corrected_text="I ask Chauntea.",
confidence=0.8,
)
with pytest.raises(AuditaValidationError):
apply_corrections(transcript, [correction], confidence_threshold=0.8)

99
tests/test_pipeline.py Normal file
View File

@@ -0,0 +1,99 @@
from pathlib import Path
import pytest
from audita.config import AuditaConfig
from audita.errors import AuditaValidationError
from audita.pipeline import process_transcript
from audita.schemas import CorrectionCandidate, CorrectionSet, parse_glossary_yaml, parse_transcript_json
class FakeLLMClient:
def __init__(self, responses):
self.responses = list(responses)
self.calls = 0
def create_corrections(self, messages, config):
self.calls += 1
return self.responses.pop(0)
def _config(tmp_path):
return AuditaConfig(
api_key="key",
max_section_tokens=16000,
confidence_threshold=0.8,
max_retries=3,
work_dir=tmp_path / "work",
)
def _glossary():
return parse_glossary_yaml(
"""
glossary:
- name: "Chauntea"
category: deity
summary: "Chauntea is a deity."
"""
)
def _transcript():
return parse_transcript_json(
"""
[
{"speaker": "Eric", "start": 0.0, "end": 1.0, "text": "I ask Chontia."}
]
"""
)
def test_pipeline_processes_with_fake_llm_and_cleans_work_dir(tmp_path):
correction = CorrectionCandidate(
segment_index=0,
speaker="Eric",
start=0.0,
end=1.0,
original_text="I ask Chontia.",
corrected_text="I ask Chauntea.",
confidence=0.95,
)
fake_client = FakeLLMClient([CorrectionSet(corrections=[correction])])
revised = process_transcript(
_transcript(),
_glossary(),
_config(tmp_path),
llm_client=fake_client,
)
assert revised[0].text == "I ask Chauntea."
assert fake_client.calls == 1
assert list((tmp_path / "work").iterdir()) == []
def test_pipeline_preserves_work_dir_on_failure(tmp_path):
correction = CorrectionCandidate(
segment_index=0,
speaker="Eric",
start=0.0,
end=1.0,
original_text="Different text.",
corrected_text="I ask Chauntea.",
confidence=0.95,
)
fake_client = FakeLLMClient([CorrectionSet(corrections=[correction])])
with pytest.raises(AuditaValidationError):
process_transcript(
_transcript(),
_glossary(),
_config(tmp_path),
llm_client=fake_client,
)
preserved = list((tmp_path / "work").iterdir())
assert len(preserved) == 1
assert (Path(preserved[0]) / "section-0000.json").exists()

76
tests/test_validation.py Normal file
View File

@@ -0,0 +1,76 @@
import pytest
from audita.errors import AuditaValidationError
from audita.schemas import parse_glossary_yaml, parse_transcript_json
def test_valid_transcript_parses():
segments = parse_transcript_json(
"""
[
{"speaker": "Eric", "start": 0.0, "end": 1.25, "text": "Then Lyra."}
]
"""
)
assert len(segments) == 1
assert segments[0].speaker == "Eric"
def test_transcript_rejects_extra_fields():
with pytest.raises(AuditaValidationError):
parse_transcript_json(
"""
[
{"speaker": "Eric", "start": 0.0, "end": 1.25, "text": "Hi", "extra": true}
]
"""
)
def test_transcript_rejects_bad_timestamps():
with pytest.raises(AuditaValidationError):
parse_transcript_json(
"""
[
{"speaker": "Eric", "start": 2.0, "end": 1.0, "text": "Hi"}
]
"""
)
def test_transcript_rejects_empty_input():
with pytest.raises(AuditaValidationError):
parse_transcript_json("[]")
def test_valid_glossary_parses():
glossary = parse_glossary_yaml(
"""
glossary:
- name: "Lyra"
category: npc
summary: "Lyra is a hostile NPC."
"""
)
assert glossary.glossary[0].name == "Lyra"
def test_glossary_rejects_empty_entries():
with pytest.raises(AuditaValidationError):
parse_glossary_yaml("glossary: []")
def test_glossary_rejects_extra_fields():
with pytest.raises(AuditaValidationError):
parse_glossary_yaml(
"""
glossary:
- name: "Lyra"
category: npc
summary: "Lyra is a hostile NPC."
extra: "nope"
"""
)