API reference
This page exposes the internal modules through Sphinx autodoc. The public command-line interface is more stable than the Python internals.
Stability notes
Stable public API: The CLI commands (
booktx init,booktx extract,booktx translate next, etc.) and their JSON output shapes are the primary stable interface.Stable models: Pydantic models in
booktx.models(Chunk, Record, TranslationStore, TranslationTask, Manifest) are serialization contracts used by the CLI and external tools.Service modules:
booktx.status,booktx.tasks,booktx.submissions,booktx.acceptance,booktx.rendering,booktx.io_utilscontain the extracted service logic. Their public functions are stable within a release cycle.Internal helpers:
booktx.config,booktx.context,booktx.validate,booktx.build,booktx.chunking,booktx.placeholdersare intended stable but may change between minor releases.Legacy:
booktx.html_iocontains shared XHTML helpers that may be consolidated in future releases.
Project configuration and path resolution for legacy and profile layouts.
booktx now supports two project shapes.
Legacy single-layout projects:
book/
source/
.booktx/
config.toml
manifest.json
names.json
chunks/
context.json
context.md
identity.json
translation-store.json
translation-version-ledger.json
tasks/
ingest/
translated/
reports/
output/
Profile-layout projects:
book/
source/
.booktx/
source-config.toml
source-manifest.json
names.json
chapter-map.json
chunks/
translations/
<profile>/
config.toml
identity.json
context.json
context.md
translation-store.json
translation-version-ledger.json
tasks/
ingest/
translated/
reports/
output/
The shared .booktx/ tree is source-derived state only. Mutable translation
state lives under the explicitly resolved profile.
- exception booktx.config.BooktxError(code: str, message: str)[source]
User-facing error from booktx. Carries a stable
codeattribute.
- class booktx.config.Project(root: Path, source_dir: Path, booktx_dir: Path, translations_dir: Path, source_config_path: Path, config_path: Path, manifest_path: Path, names_path: Path, chunks_dir: Path, chapter_map_path: Path, profile: str | None, profile_dir: Path | None, profile_config_path: Path | None, context_json_path: Path | None, context_md_path: Path | None, identity_json_path: Path | None, store_path: Path | None, ledger_path: Path | None, translated_dir: Path | None, tasks_dir: Path | None, ingest_dir: Path | None, reports_dir: Path | None, output_dir: Path | None, source_config: SourceConfig, profile_config: ProfileConfig | None, config: ProjectConfig, layout_version: Literal['legacy', 'profiles'])[source]
Resolved project paths for either the legacy or profile layout.
- root: Path
- source_dir: Path
- booktx_dir: Path
- translations_dir: Path
- source_config_path: Path
- config_path: Path
- manifest_path: Path
- names_path: Path
- chunks_dir: Path
- chapter_map_path: Path
- source_config: SourceConfig
- profile_config: ProfileConfig | None
- config: ProjectConfig
- layout_version: Literal['legacy', 'profiles']
- property source_path: Path
- __init__(root: Path, source_dir: Path, booktx_dir: Path, translations_dir: Path, source_config_path: Path, config_path: Path, manifest_path: Path, names_path: Path, chunks_dir: Path, chapter_map_path: Path, profile: str | None, profile_dir: Path | None, profile_config_path: Path | None, context_json_path: Path | None, context_md_path: Path | None, identity_json_path: Path | None, store_path: Path | None, ledger_path: Path | None, translated_dir: Path | None, tasks_dir: Path | None, ingest_dir: Path | None, reports_dir: Path | None, output_dir: Path | None, source_config: SourceConfig, profile_config: ProfileConfig | None, config: ProjectConfig, layout_version: Literal['legacy', 'profiles']) None
- booktx.config.profile_config_path(root_or_project: Project | Path | str, profile_name: str) Path[source]
- booktx.config.profile_root_marker_path(root_or_project: Project | Path | str, profile_name: str) Path[source]
- booktx.config.profile_source_cache_dir(root_or_project: Project | Path | str, profile_name: str) Path[source]
- booktx.config.load_profile_config(project_or_root: Project | Path | str, profile_name: str) ProfileConfig[source]
- booktx.config.load_profile_root_marker(profile_root: Path | str) ProfileRootMarker[source]
- booktx.config.write_profile_config(project_or_root: Project | Path | str, cfg: ProfileConfig) None[source]
- booktx.config.write_profile_root_marker(project_or_root: Project | Path | str, profile_name: str, *, profile_config: ProfileConfig | None = None) ProfileRootMarker[source]
- booktx.config.resolve_profile_name(project: Project, explicit_profile: str | None, *, require_profile: bool, operation: str = 'command') str | None[source]
- booktx.config.load_project(root: Path | str, *, profile: str | None = None, require_profile: bool = False) Project[source]
- booktx.config.init_source_project(target: Path, *, source_language: str = 'en', source_file: Path | str | None = None, chunk_size: int = 50) Project[source]
- booktx.config.init_project(target: Path, *, target_language: str = '', profile_name: str | None = None, source_language: str = 'en', source_file: Path | str | None = None, chunk_size: int = 50) Project[source]
- booktx.config.create_profile(root: Path | str, profile_name: str, *, target_language: str, target_locale: str | None = None, actor: str | None = None, harness: str | None = None, model: str | None = None, output_filename: str | None = None, kind: Literal['translation', 'pass-through', 'selection'] = 'translation') Project[source]
- booktx.config.migrate_current_project(root: Path | str, profile_name: str, *, target_language: str | None = None, target_locale: str | None = None, actor: str | None = None, harness: str | None = None, model: str | None = None, dry_run: bool = False) dict[str, object][source]
- booktx.config.load_translation_store(project: Project) TranslationStoreV2[source]
- booktx.config.load_translation_version_ledger(project: Project) TranslationVersionLedger[source]
- booktx.config.load_identity(project: Project) TranslationIdentity | None[source]
- booktx.config.write_translation_store(project: Project, store: TranslationStoreV2) None[source]
- booktx.config.write_translation_version_ledger(project: Project, ledger: TranslationVersionLedger) None[source]
- booktx.config.write_identity(project: Project, identity: TranslationIdentity) None[source]
- booktx.config.translation_todo_dir(project: Project) Path[source]
Profile-local directory for durable agent-run todo files.
Returns
translations/<profile>/todos/. RaisesBooktxErrorif no profile is selected.
- booktx.config.review_todo_dir(project: Project) Path[source]
Profile-local directory for durable review todo files.
Returns
translations/<profile>/review-todos/. RaisesBooktxErrorif no profile is selected.
- booktx.config.load_translation_task(project: Project, task_id: str) TranslationTask | None[source]
- booktx.config.write_translation_task(project: Project, task: TranslationTask) None[source]
- booktx.config.translation_review_dir(project: Project) Path[source]
Profile-local directory for durable review task artifacts.
Returns
translations/<profile>/reviews/. RaisesBooktxErrorif no profile is selected.
- booktx.config.translation_review_source_block_path(project: Project, review_task_id: str) Path[source]
- booktx.config.translation_review_ingest_block_path(project: Project, review_task_id: str) Path[source]
- booktx.config.translation_source_index_path(project: Project) Path[source]
Profile-local generated source-only editor index path.
Returns
translations/<profile>/source-index.json. The file is a rebuildable generated artifact, never canonical state.
- booktx.config.translation_target_index_path(project: Project) Path[source]
Profile-local generated target-only editor index path.
Returns
translations/<profile>/target-index.json. The file is a rebuildable generated artifact, never canonical state.
- booktx.config.translation_source_target_index_path(project: Project) Path[source]
Profile-local generated source/target side-by-side editor index path.
Returns
translations/<profile>/source-target-index.json. The file is a rebuildable generated artifact, never canonical state.
- booktx.config.source_analysis_path(project: Project) Path[source]
Project-root canonical source-analysis JSON evidence path.
Returns
.booktx/source-analysis.json. The file is rebuildable generated evidence (authoritative JSON report), never canonical translation state.
- booktx.config.source_analysis_markdown_path(project: Project) Path[source]
Project-root generated source-analysis Markdown view path.
Returns
.booktx/source-analysis.md. Generated from the canonical JSON report; never authoritative.
- booktx.config.source_analysis_decisions_path(project: Project) Path[source]
Project-root durable source-analysis review/provenance sidecar.
- booktx.config.profile_source_analysis_path(project: Project) Path[source]
Profile-local generated source-analysis snapshot JSON path.
Returns
translations/<profile>/source-analysis.json. The snapshot embeds the same report payload andanalysis_sha256as the canonical project-root report inside a profile-scoped envelope. Rebuildable.
- booktx.config.profile_source_analysis_markdown_path(project: Project) Path[source]
Profile-local generated source-analysis snapshot Markdown path.
Returns
translations/<profile>/source-analysis.md. Generated from the profile snapshot’s embedded report; never authoritative.
- booktx.config.context_sync_ledger_path(project: Project) Path[source]
Profile-local context-sync audit ledger path.
- booktx.config.load_context_sync_ledger(project: Project) ContextSyncLedger[source]
- booktx.config.write_context_sync_ledger(project: Project, ledger: ContextSyncLedger) None[source]
- booktx.config.translation_selection_ledger_path(project: Project) Path[source]
Profile-local selection provenance ledger path.
- booktx.config.load_translation_selection_ledger(project: Project) TranslationSelectionLedger[source]
- booktx.config.write_translation_selection_ledger(project: Project, ledger: TranslationSelectionLedger) None[source]
- booktx.config.canonical_language_key(language: str) str[source]
Return a canonical language shard key such as
deorde-DE.
- booktx.config.termbase_language_keys(project: Project, language: str | None = None) list[str][source]
Resolve the base-plus-locale language-key sequence for termbase reads.
- booktx.config.global_termbase_dir() Path[source]
Directory containing user-global translation termbase shards.
- booktx.config.global_termbase_path(language_key: str) Path[source]
Path to one user-global translation termbase shard.
- booktx.config.project_termbase_path(project: Project, language_key: str) Path[source]
Path to one project/series termbase shard.
- booktx.config.profile_termbase_path(project: Project, language_key: str) Path[source]
Path to one profile-local termbase override shard.
- booktx.config.profile_termbase_snapshot_path(project: Project, language_key: str) Path[source]
Path to one frozen termbase snapshot shard for profile-root isolation.
- booktx.config.judge_task_dir(project: Project) Path[source]
Profile-local directory for durable judge task artifacts.
- booktx.config.judge_ingest_dir(project: Project) Path[source]
Profile-local directory for editable judge ingest artifacts.
- booktx.config.load_translation_review_task(project: Project, review_task_id: str) TranslationReviewTask | None[source]
- booktx.config.write_translation_review_task(project: Project, task: TranslationReviewTask) None[source]
- booktx.config.find_source_file(project: Project, *, persist_discovery: bool = True) Path[source]
Resolve the project source file.
By default this persists a newly discovered source file/format back into the source config (a write side effect). Pass
persist_discovery=Falsefor read-only lookups such as status overviews and hashing, so a getter no longer mutates project state.
- booktx.config.project_storage_root(project: Project) Path[source]
Return the root used for persisted profile-local path strings.
- booktx.config.stored_path(project: Project, path: Path) str[source]
Persist a path relative to the resolved profile when available.
- booktx.config.resolve_stored_path(project: Project, stored_rel_path: str) Path[source]
Resolve a persisted path against legacy root-relative or profile-relative roots.
Pydantic data models for the booktx translation contract.
This module defines the JSON shapes that cross the boundary between booktx and the translating coding agent:
Source chunk ->
chunks/NNNN.json(written bybooktx extract)Translated chunk ->
translated/NNNN.json(written by the agent)
Both must round-trip through JSON with stable field names and ordering.
- class booktx.models.Placeholder(*, token: str, original: str, kind: Literal['name', 'tag'] = 'tag')[source]
A non-translatable span that was replaced by a token during extraction.
tokenis the exact placeholder text that appears in the source field (e.g.__NAME_001__or__TAG_001__);originalis the verbatim text that must be restored verbatim during build.- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- token: str
- original: str
- kind: Literal['name', 'tag']
- class booktx.models.Record(*, id: str, source: str, protected_terms: list[str] = <factory>, placeholders: list[Placeholder] = <factory>, span_index: int | None = None, span_record_index: int | None = None, source_markup: Literal['plain:v1', 'epub-inline-xhtml: v1'] = 'plain:v1')[source]
A single translatable source record inside a source chunk.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- id: str
- source: str
- placeholders: list[Placeholder]
- source_markup: Literal['plain:v1', 'epub-inline-xhtml:v1']
- class booktx.models.TranslatedRecord(*, id: str, version: str | None = None, target: str)[source]
A single translated record inside a translated chunk.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- id: str
- target: str
- class booktx.models.Chunk(*, schema_version: int = 2, chunk_id: str, chunk_size: Annotated[int, ~annotated_types.Ge(ge=1)] = 50, record_id_scheme: str = 'chunk-local:v1', source_language: str, target_language: str = '', records: list[Record] = <factory>)[source]
A source chunk file (
chunks/NNNN.json).- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- schema_version: int
- chunk_id: str
- chunk_size: int
- record_id_scheme: str
- source_language: str
- target_language: str
- class booktx.models.TranslatedChunk(*, chunk_id: str, records: list[TranslatedRecord] = <factory>, **extra_data: Any)[source]
A translated chunk file (
translated/NNNN.json).- model_config = {'extra': 'allow'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- chunk_id: str
- records: list[TranslatedRecord]
- class booktx.models.StoredTranslationRecord(*, chunk_id: str, source_sha256: str = '', target: str, status: Literal['accepted'] = 'accepted', updated_at: str = '')[source]
One accepted record stored in
translation-store.json.- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- chunk_id: str
- source_sha256: str
- target: str
- status: Literal['accepted']
- updated_at: str
- class booktx.models.TranslationStore(*, version: int = 1, source_sha256: str = '', records: dict[str, ~booktx.models.StoredTranslationRecord]=<factory>)[source]
Primary record-level translation state owned by booktx.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- version: int
- source_sha256: str
- records: dict[str, StoredTranslationRecord]
- class booktx.models.TranslationCandidate(*, version: int, subversion: int, version_ref: str, baseline_ref: str | None = None, baseline_sha256: str | None = None, context_view_sha256: str | None = None, context_view_path: str | None = None, context_notes_scope: str | None = None, context_target_chapter_id: str | None = None, context_notes_through_chapter_id: str | None = None, target: str, status: str = 'accepted', created_at: str, updated_at: str, reviewed_at: str | None = None, reviewed_by: str | None = None, review_note: str | None = None)[source]
One candidate translation stored under a record version.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- version: int
- subversion: int
- version_ref: str
- target: str
- status: str
- created_at: str
- updated_at: str
- class booktx.models.StoredTranslationRecordV2(*, chunk_id: int, part_id: int, source_sha256: str, source: str, active_version: str | None = None, active_review: str | None = None, versions: list[TranslationCandidate] = <factory>, reviews: list[TranslationReviewCandidate] = <factory>)[source]
One source record with nested versioned translation candidates.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- chunk_id: int
- part_id: int
- source_sha256: str
- source: str
- versions: list[TranslationCandidate]
- reviews: list[TranslationReviewCandidate]
- class booktx.models.TranslationStoreV2(*, version: Literal[2] = 2, source_sha256: str = '', records: dict[str, ~booktx.models.StoredTranslationRecordV2]=<factory>)[source]
Primary nested translation state for booktx.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- version: Literal[2]
- source_sha256: str
- records: dict[str, StoredTranslationRecordV2]
- class booktx.models.TranslationSubversionLedgerEntry(*, version: int, subversion: int, version_ref: str, context_sha256: str, context_path: str = '.booktx/context.json', baseline_sha256: str | None = None, baseline_path: str | None = None, legacy_full_context_sha256: str | None = None, legacy_full_context_path: str | None = None, context_label: str | None = None, created_at: str, updated_at: str, notes: str | None = None, forced: bool = False)[source]
One context-scoped subversion entry inside a major track.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- version: int
- subversion: int
- version_ref: str
- context_sha256: str
- context_path: str
- created_at: str
- updated_at: str
- forced: bool
- class booktx.models.TranslationTrackLedgerEntry(*, version: int, actor: str, harness: str, model: str, label: str | None = None, created_at: str, updated_at: str, subversions: dict[str, ~booktx.models.TranslationSubversionLedgerEntry]=<factory>)[source]
One major version track storing stable identity and subversions.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- version: int
- actor: str
- harness: str
- model: str
- created_at: str
- updated_at: str
- subversions: dict[str, TranslationSubversionLedgerEntry]
- class booktx.models.TranslationVersionLedger(*, version: Literal[1] = 1, source_sha256: str = '', active_version: str | None = None, tracks: dict[str, ~booktx.models.TranslationTrackLedgerEntry]=<factory>)[source]
Project-wide ledger assigning meaning to translation versions.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- version: Literal[1]
- source_sha256: str
- tracks: dict[str, TranslationTrackLedgerEntry]
- class booktx.models.TranslationIdentity(*, actor: str, harness: str, model: str)[source]
Stored defaults for new translation-version work.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- actor: str
- harness: str
- model: str
- class booktx.models.ApplicableTermbaseExampleSnapshot(*, source: str, good_target: str = '', bad_target: str = '', note: str = '')[source]
One compact termbase example persisted with a task snapshot.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- source: str
- good_target: str
- bad_target: str
- note: str
- class booktx.models.ApplicableTermbaseUsageRuleSnapshot(*, rule_id: str, context_id: str, source_cue: str | None = None, source_regex: str | None = None, required_target_literals: list[str] = <factory>, required_target_regexes: list[str] = <factory>, allowed_target_literals: list[str] = <factory>, allowed_target_regexes: list[str] = <factory>, forbidden_target_literals: list[str] = <factory>, forbidden_target_regexes: list[str] = <factory>, severity: Literal['info', 'warn', 'error']='warn', prompt: str = '', fallback: bool = False)[source]
One compact termbase usage rule persisted with a task snapshot.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- rule_id: str
- context_id: str
- severity: Literal['info', 'warn', 'error']
- prompt: str
- fallback: bool
- class booktx.models.ApplicableTermbaseEntrySnapshot(*, entry_id: str, kind: ~typing.Literal['flat_term', 'contextual_term', 'phrase_preference', 'collocation_preference', 'word_sense', 'style_preference', 'forbidden_literalism', 'world_term'] = 'phrase_preference', source: str, source_variants: list[str] = <factory>, source_regex: str | None = None, source_language: str = 'en', case_sensitive: bool = False, matched_source_cue: str, matched_source_span: tuple[int, int], target_preferred: list[str] = <factory>, target_allowed: list[str] = <factory>, target_forbidden: list[str] = <factory>, target_regex_forbidden: list[str] = <factory>, preferred_policy: ~typing.Literal['off', 'advisory', 'required'] = 'off', severity: ~typing.Literal['info', 'warn', 'error'] = 'warn', sense: str = '', rationale: str = '', examples: list[~booktx.models.ApplicableTermbaseExampleSnapshot] = <factory>, usage_rules: list[~booktx.models.ApplicableTermbaseUsageRuleSnapshot] = <factory>)[source]
One applicable termbase entry snapshot persisted on a task record.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- entry_id: str
- kind: Literal['flat_term', 'contextual_term', 'phrase_preference', 'collocation_preference', 'word_sense', 'style_preference', 'forbidden_literalism', 'world_term']
- source: str
- source_language: str
- case_sensitive: bool
- matched_source_cue: str
- preferred_policy: Literal['off', 'advisory', 'required']
- severity: Literal['info', 'warn', 'error']
- sense: str
- rationale: str
- examples: list[ApplicableTermbaseExampleSnapshot]
- usage_rules: list[ApplicableTermbaseUsageRuleSnapshot]
- class booktx.models.ApplicableTermbaseFindingSnapshot(*, entry_id: str, rule_id: str = '', context_id: str = '', status: ~typing.Literal['forbidden_target', 'preferred_missing', 'clean'], rule_code: str, severity: ~typing.Literal['info', 'warn', 'error'], reason: str = '', target_forbidden_found: list[str] = <factory>, target_required_found: list[str] = <factory>, target_allowed_found: list[str] = <factory>, effective_candidate_ref: str = '', prompt: str = '', fallback: bool = False)[source]
One deterministic termbase audit finding persisted on a review task record.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- entry_id: str
- rule_id: str
- context_id: str
- status: Literal['forbidden_target', 'preferred_missing', 'clean']
- rule_code: str
- severity: Literal['info', 'warn', 'error']
- reason: str
- effective_candidate_ref: str
- prompt: str
- fallback: bool
- class booktx.models.TranslationTaskRecord(*, id: str, chunk_id: str, source: str, protected_terms: list[str] = <factory>, placeholders: list[Placeholder] = <factory>, applicable_termbase: list[ApplicableTermbaseEntrySnapshot] = <factory>)[source]
A record assigned to a CLI-created translation task.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- id: str
- chunk_id: str
- source: str
- placeholders: list[Placeholder]
- applicable_termbase: list[ApplicableTermbaseEntrySnapshot]
- class booktx.models.TranslationTask(*, version: int = 1, task_id: str, unit: ~typing.Literal['paragraph', 'batch', 'chunk', 'chapter'], chapter_id: str = '', chapter_title: str = '', profile: str = '', source_language: str, target_language: str, target_locale: str = '', translation_version: str | None = None, baseline_ref: str | None = None, baseline_sha256: str | None = None, context_sha256: str | None = None, context_view_sha256: str | None = None, context_view_path: str | None = None, mandatory_glossary_sha256: str | None = None, applicable_termbase_sha256: str | None = None, context_notes_scope: str | None = None, context_target_chapter_id: str | None = None, context_notes_through_chapter_id: str | None = None, source_sha256: str | None = None, profile_config_sha256: str | None = None, source_config_sha256: str | None = None, source_words: int = 0, record_count: int = 0, requested_max_words: int | None = None, todo_id: str | None = None, records: list[~booktx.models.TranslationTaskRecord] = <factory>)[source]
A persisted work item returned by
booktx translate next.- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- version: int
- task_id: str
- unit: Literal['paragraph', 'batch', 'chunk', 'chapter']
- chapter_id: str
- chapter_title: str
- profile: str
- source_language: str
- target_language: str
- target_locale: str
- source_words: int
- record_count: int
- records: list[TranslationTaskRecord]
- class booktx.models.StatusTotals(*, source_words: int = 0, translated_words: int = 0, remaining_words: int = 0, records_total: int = 0, records_translated: int = 0, records_remaining: int = 0, chunks_total: int = 0, chunks_complete: int = 0, chunks_partial: int = 0, chunks_pending: int = 0, chapters_total: int = 0, chapters_complete: int = 0, chapters_partial: int = 0, chapters_pending: int = 0, invalid_translation_files: int = 0, stale_translation_files: int = 0)[source]
Aggregate translation coverage totals.
Used by
status --jsonand persisted inTranslationTodo.start_totals. Keep this model inmodels.pyso durable todo validation never depends on a late Pydantic forward-reference rebuild.- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- source_words: int
- translated_words: int
- remaining_words: int
- records_total: int
- records_translated: int
- records_remaining: int
- chunks_total: int
- chunks_complete: int
- chunks_partial: int
- chunks_pending: int
- chapters_total: int
- chapters_complete: int
- chapters_partial: int
- chapters_pending: int
- invalid_translation_files: int
- stale_translation_files: int
- class booktx.models.TranslationTodoChapter(*, chapter_id: str, title: str = '', status: str, records_total: int, records_translated_at_start: int, records_remaining_at_start: int, source_words_remaining_at_start: int, pending_chunk_ids: list[str] = <factory>)[source]
One chapter selected for a bounded agent translation run.
Carries only coverage/state at the moment the todo was created; it never embeds source text. Source text belongs in per-task
*.source.block.txt.- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- chapter_id: str
- title: str
- status: str
- records_total: int
- records_translated_at_start: int
- records_remaining_at_start: int
- source_words_remaining_at_start: int
- class booktx.models.TranslationTodo(*, version: Literal[1] = 1, todo_id: str, profile: str, target_language: str, target_locale: str = '', chapters_requested: int, batch_words: int, max_run_words: int | None = None, include_current: bool = True, created_at: str, baseline_ref: str | None = None, baseline_sha256: str | None = None, context_sha256: str | None = None, mandatory_glossary_sha256: str | None = None, source_sha256: str | None = None, start_totals: StatusTotals, chapters: list[TranslationTodoChapter] = <factory>)[source]
A durable run-control artifact for a bounded multi-chapter agent run.
Written by
booktx translate todo-nextundertranslations/<profile>/todos/<todo_id>.{json,md}. This is NOT a translation submission: it tells the agent how many chapters to complete, the per-task word budget, and the stop conditions.- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- version: Literal[1]
- todo_id: str
- profile: str
- target_language: str
- target_locale: str
- chapters_requested: int
- batch_words: int
- include_current: bool
- created_at: str
- start_totals: StatusTotals
- chapters: list[TranslationTodoChapter]
- class booktx.models.NamesFile(*, protected_terms: list[str] = <factory>)[source]
The
.booktx/names.jsonfile holding manually protected terms.- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class booktx.models.SourceAnalysisPatternsConfig(*, world_morphemes: list[str] = <factory>, include_regex: list[str] = <factory>, exclude_regex: list[str] = <factory>)[source]
Optional source-analysis pattern configuration.
Stored under
[source_analysis.patterns]in.booktx/source-config.toml.- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class booktx.models.SourceAnalysisGenericLemmasConfig(*, extra: list[str] = <factory>)[source]
Optional project-local generic-lemma additions for source analysis.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class booktx.models.SourceAnalysisConfig(*, include_singletons: bool = True, max_per_bucket: Annotated[int, ~annotated_types.Ge(ge=1)] = 80, min_risk_score: float = 3.0, patterns: SourceAnalysisPatternsConfig = <factory>, generic_lemmas: SourceAnalysisGenericLemmasConfig = <factory>)[source]
Optional translation-risk mining configuration for source analysis.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- include_singletons: bool
- max_per_bucket: int
- min_risk_score: float
- patterns: SourceAnalysisPatternsConfig
- generic_lemmas: SourceAnalysisGenericLemmasConfig
- class booktx.models.SourceConfig(*, version: Literal[1] = 1, source_language: str = 'en', source_file: str = '', format: str = 'markdown', chunk_size: Annotated[int, Ge(ge=1)] = 50, source_analysis: SourceAnalysisConfig | None = None)[source]
Source-only config stored in
.booktx/source-config.toml.- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- version: Literal[1]
- source_language: str
- source_file: str
- format: str
- chunk_size: int
- source_analysis: SourceAnalysisConfig | None
- class booktx.models.ProfileIdentityConfig(*, actor: str = 'user:unknown', harness: str = 'booktx', model: str = 'human')[source]
Initial identity defaults captured when a profile is created.
This is not the live identity. After creation, the authoritative identity lives in
translations/<profile>/identity.jsonand is updated bybooktx model set/actor set/harness set. Profile list and show render the resolvedidentity.jsonvalue, not this field.- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- actor: str
- harness: str
- model: str
- class booktx.models.ProfileConfig(*, version: Literal[1] = 1, kind: Literal['translation', 'pass-through', 'selection']='translation', profile: str, source_language: str = 'en', target_language: str, target_locale: str | None = None, output_filename: str | None = None, identity: ProfileIdentityConfig = <factory>, selection: SelectionConfig | None = None, quality_review: QualityReviewConfig | None = None, indexes: IndexesConfig | None = None, epub_output: EpubOutputConfig | None = None)[source]
Per-profile translation config stored in
translations/<profile>/config.toml.- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- version: Literal[1]
- kind: Literal['translation', 'pass-through', 'selection']
- profile: str
- source_language: str
- target_language: str
- identity: ProfileIdentityConfig
Initial identity defaults; the live identity is identity.json (see above).
- selection: SelectionConfig | None
- quality_review: QualityReviewConfig | None
- indexes: IndexesConfig | None
- epub_output: EpubOutputConfig | None
- class booktx.models.ProfileRootMarker(*, schema: Literal['booktx.profile-root.v1'] = 'booktx.profile-root.v1', profile: str, source_id: str, target_language: str, target_locale: str, mode_hint: Literal['profile-root'] = 'profile-root')[source]
Profile-root marker for isolated runtime resolution.
- model_config = {'extra': 'forbid', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- schema_name: Literal['booktx.profile-root.v1']
- profile: str
- source_id: str
- target_language: str
- target_locale: str
- mode_hint: Literal['profile-root']
- class booktx.models.ProjectConfig(*, source_language: str = 'en', target_language: str = '', target_locale: str | None = None, output_filename: str | None = None, source_file: str = '', format: str = 'markdown', chunk_size: Annotated[int, Ge(ge=1)] = 50, epub_output: EpubOutputConfig | None = None)[source]
Effective runtime config, or the legacy
.booktx/config.tomlshape.The TOML file mirrors these field names exactly so it stays human-editable.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- source_language: str
- target_language: str
- source_file: str
- format: str
- chunk_size: int
- epub_output: EpubOutputConfig | None
- class booktx.models.EpubSpanRef(*, span_index: int, block_id: str, document_href: str, spine_index: int | None = None, tag_name: str, source_text: str, source_text_sha256: str, source_char_start: int | None = None, source_char_end: int | None = None, placeholders: list[Placeholder] = <factory>, protected_terms: list[str] = <factory>, source_view_text: str = '', source_view_sha256: str = '', source_markup: str = 'plain:v1', inline_skeleton: list[dict[str, ~typing.Any]]=<factory>, chapter_id: str | None = None, chapter_title: str | None = None)[source]
Stored span-to-EPUB-block mapping for the migrated EPUB pipeline.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- span_index: int
- block_id: str
- document_href: str
- tag_name: str
- source_text: str
- source_text_sha256: str
- placeholders: list[Placeholder]
- source_view_text: str
- source_view_sha256: str
- source_markup: str
Stored navigation metadata for EPUB chapter detection.
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class booktx.models.EpubTemplateData(*, pipeline: str, epub2text_schema: str, text2epub_manifest: dict[str, ~typing.Any]=<factory>, spans: list[EpubSpanRef] = <factory>, navigation: list[EpubNavigationRef] = <factory>, chapter_mapping: Literal['legacy', 'epub2text-block-v1']='legacy')[source]
Typed payload stored in
Manifest.templatefor EPUB v2 projects.- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- pipeline: str
- epub2text_schema: str
- spans: list[EpubSpanRef]
- chapter_mapping: Literal['legacy', 'epub2text-block-v1']
- class booktx.models.ManifestSource(*, filename: str, format: str, source_language: str, target_language: str = '', sha256: str = '')[source]
Metadata about the source document recorded in the manifest.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- filename: str
- format: str
- source_language: str
- target_language: str
- sha256: str
- class booktx.models.Manifest(*, version: int = 1, source: ManifestSource, chunk_count: int = 0, record_count: int = 0, chunk_size: Annotated[int, ~annotated_types.Ge(ge=1)] = 50, record_id_scheme: str = 'chunk-local:v1', segmenter: dict[str, ~typing.Any]=<factory>, names_sha256: str = '', template: dict[str, ~typing.Any]=<factory>)[source]
The
.booktx/manifest.jsoncontent.- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- version: int
- source: ManifestSource
- chunk_count: int
- record_count: int
- chunk_size: int
- record_id_scheme: str
- names_sha256: str
- class booktx.models.TranslationReviewCandidate(*, pass_number: Annotated[int, Ge(ge=1)], run_number: Annotated[int, Ge(ge=1)], review_ref: str, base_kind: Literal['translation', 'review'], base_ref: str, base_target_sha256: str, target: str, target_sha256: str, status: Literal['accepted', 'rejected', 'superseded'] = 'accepted', created_at: str, updated_at: str, reviewed_by: str | None = None, review_model: str | None = None, review_task_id: str | None = None, review_note: str | None = None, context_view_sha256: str | None = None, context_view_path: str | None = None, review_window_sha256: str | None = None, review_policy_sha256: str | None = None)[source]
One reviewed target stored separately from translation versions.
A review candidate records one quality-improved output of a numbered review pass (
R<pass>.<run>). It is derived from a base candidate, which is either a translation version (base_kind="translation") or an earlier review candidate (base_kind="review"). Review candidates never overwrite translation versions.- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- pass_number: int
- run_number: int
- review_ref: str
- base_kind: Literal['translation', 'review']
- base_ref: str
- base_target_sha256: str
- target: str
- target_sha256: str
- status: Literal['accepted', 'rejected', 'superseded']
- created_at: str
- updated_at: str
- class booktx.models.ReviewContextRecord(*, id: str, chunk_id: str, source: str, effective_target: str | None = None, effective_ref: str | None = None, role: Literal['before', 'selected', 'after'])[source]
One neighbor or selected record in a review task context window.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- id: str
- chunk_id: str
- source: str
- role: Literal['before', 'selected', 'after']
- class booktx.models.TranslationReviewTaskRecord(*, id: str, chunk_id: str, source: str, base_kind: ~typing.Literal['translation', 'review'], base_ref: str, base_target: str, base_target_sha256: str, review_ref: str, pass_number: int, review_window_sha256: str, before: list[~booktx.models.ReviewContextRecord] = <factory>, after: list[~booktx.models.ReviewContextRecord] = <factory>, applicable_termbase: list[~booktx.models.ApplicableTermbaseEntrySnapshot] = <factory>, termbase_findings: list[~booktx.models.ApplicableTermbaseFindingSnapshot] = <factory>)[source]
One selected record in a review task.
Carries the base candidate being reviewed plus the review_ref this task will create, so a single task can safely include records whose next run number differs.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- id: str
- chunk_id: str
- source: str
- base_kind: Literal['translation', 'review']
- base_ref: str
- base_target: str
- base_target_sha256: str
- review_ref: str
- pass_number: int
- review_window_sha256: str
- before: list[ReviewContextRecord]
- after: list[ReviewContextRecord]
- applicable_termbase: list[ApplicableTermbaseEntrySnapshot]
- termbase_findings: list[ApplicableTermbaseFindingSnapshot]
- class booktx.models.TranslationReviewTask(*, version: Literal[1] = 1, review_task_id: str, profile: str, chapter_id: str, chapter_title: str = '', pass_number: int, pass_name: str = '', pass_instructions: str = '', source_language: str, target_language: str, target_locale: str = '', context_view_sha256: str | None = None, context_view_path: str | None = None, source_sha256: str | None = None, profile_config_sha256: str | None = None, source_config_sha256: str | None = None, review_policy_sha256: str | None = None, mandatory_glossary_sha256: str | None = None, applicable_termbase_sha256: str | None = None, before_records: int, after_records: int, source_words: int, record_count: int, created_at: str, records: list[TranslationReviewTaskRecord] = <factory>)[source]
A persisted review work item returned by
booktx review next.- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- version: Literal[1]
- review_task_id: str
- profile: str
- chapter_id: str
- chapter_title: str
- pass_number: int
- pass_name: str
- pass_instructions: str
- source_language: str
- target_language: str
- target_locale: str
- before_records: int
- after_records: int
- source_words: int
- record_count: int
- created_at: str
- records: list[TranslationReviewTaskRecord]
- class booktx.models.ReviewPassConfig(*, pass_number: Annotated[int, Ge(ge=1)], name: str = '', enabled: bool = True, mode: Literal['manual', 'after_chapter', 'before_build'] = 'manual', enforce: Literal['off', 'warn', 'error'] = 'off', base: Literal['active_translation', 'active_review'] = 'active_translation', required_base_pass: int | None = None, before_records: Annotated[int, Ge(ge=0), Le(le=20)] = 2, after_records: Annotated[int, Ge(ge=0), Le(le=20)] = 2, batch_words: Annotated[int, Ge(ge=1)] = 900, instructions: str = '', include_untranslated_neighbors: bool = False)[source]
Configuration for one numbered review pass.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- pass_number: int
- name: str
- enabled: bool
- mode: Literal['manual', 'after_chapter', 'before_build']
- enforce: Literal['off', 'warn', 'error']
- base: Literal['active_translation', 'active_review']
- before_records: int
- after_records: int
- batch_words: int
- instructions: str
- include_untranslated_neighbors: bool
- class booktx.models.ReviewTodoPass(*, pass_number: Annotated[int, Ge(ge=1)], selection: Literal['missing', 'stale', 'reviewed', 'all', 'changed-base'] = 'missing', base: str = 'active_translation')[source]
One review pass selected for a durable review todo.
Carries the pass_number, selection mode, and base mode that will be used when this pass is resumed via
review todo-resume.- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- pass_number: int
- selection: Literal['missing', 'stale', 'reviewed', 'all', 'changed-base']
- base: str
- class booktx.models.ReviewTodoChapter(*, chapter_id: str, title: str = '', status: str, eligible_records_at_start: int = 0, missing_review_at_start: int = 0, stale_review_at_start: int = 0, pending_passes: list[int] = <factory>)[source]
One chapter tracked by a durable review todo.
Captures review coverage at the moment the todo was created so callers can detect progress and drift without re-reading the entire store.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- chapter_id: str
- title: str
- status: str
- eligible_records_at_start: int
- missing_review_at_start: int
- stale_review_at_start: int
- class booktx.models.ReviewTodo(*, version: Literal[1] = 1, review_todo_id: str, profile: str, passes: list[ReviewTodoPass] = <factory>, chapters_requested: int, batch_words: int, created_at: str, source_sha256: str | None = None, profile_config_sha256: str | None = None, source_config_sha256: str | None = None, start_snapshot_sha256: str | None = None, chapters: list[ReviewTodoChapter] = <factory>)[source]
A durable run-control artifact for a bounded multi-pass review run.
Written by
booktx review todo-nextundertranslations/<profile>/review-todos/<review_todo_id>.{json,md}. This tells the agent which review passes and chapters to process, the per-pass selection and base modes, and the stop conditions.- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- version: Literal[1]
- review_todo_id: str
- profile: str
- passes: list[ReviewTodoPass]
- chapters_requested: int
- batch_words: int
- created_at: str
- chapters: list[ReviewTodoChapter]
- class booktx.models.SelectionConfig(*, sources: list[str] = <factory>, allow_edited_targets: bool = True, require_all_sources: bool = False, purpose: Literal['compare', 'revise']='compare')[source]
Selection-profile configuration stored under
[selection].- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- allow_edited_targets: bool
- require_all_sources: bool
- purpose: Literal['compare', 'revise']
- class booktx.models.ContextSyncLedgerFinding(*, section: str = '', key: str = '', action: str = '', message: str = '')[source]
One persisted finding for a context-sync ledger entry.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- section: str
- key: str
- action: str
- message: str
- class booktx.models.ContextSyncLedgerEntry(*, sync_id: str, source_profile: str, source_context_sha256: str = '', source_baseline_sha256: str = '', pack_sha256: str = '', sections: list[str] = <factory>, terms: list[str] = <factory>, question_ids: list[str] = <factory>, conflict: Literal['fail', 'keep-local', 'replace']='fail', old_baseline_sha256: str = '', new_baseline_sha256: str = '', changed: bool = False, applied_at: str, findings: list[ContextSyncLedgerFinding] = <factory>)[source]
One applied multi-profile context-sync event for a target profile.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- sync_id: str
- source_profile: str
- source_context_sha256: str
- source_baseline_sha256: str
- pack_sha256: str
- conflict: Literal['fail', 'keep-local', 'replace']
- old_baseline_sha256: str
- new_baseline_sha256: str
- changed: bool
- applied_at: str
- findings: list[ContextSyncLedgerFinding]
- class booktx.models.ContextSyncLedger(*, version: Literal[1] = 1, profile: str, entries: list[ContextSyncLedgerEntry] = <factory>)[source]
Profile-local audit log for applied context sync operations.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- version: Literal[1]
- profile: str
- entries: list[ContextSyncLedgerEntry]
- class booktx.models.JudgeTaskFinding(*, severity: Literal['info', 'warn', 'error'] = 'info', rule: str = '', message: str = '')[source]
Compact validation finding stored in judge task artifacts.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- severity: Literal['info', 'warn', 'error']
- rule: str
- message: str
- class booktx.models.JudgeTaskCandidate(*, label: str, profile: str, target_language: str, target_locale: str = '', selected_kind: ~typing.Literal['translation', 'review'], selected_ref: str, version_ref: str | None = None, review_ref: str | None = None, target: str, target_sha256: str, validation_findings: list[~booktx.models.JudgeTaskFinding] = <factory>)[source]
One candidate translation shown to the judge for a record.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- label: str
- profile: str
- target_language: str
- target_locale: str
- selected_kind: Literal['translation', 'review']
- selected_ref: str
- target: str
- target_sha256: str
- validation_findings: list[JudgeTaskFinding]
- class booktx.models.JudgeTaskRecord(*, id: str, chunk_id: str, source: str, source_sha256: str, applicable_termbase: list[ApplicableTermbaseEntrySnapshot] = <factory>, applicable_glossary: list[ApplicableGlossaryEntrySnapshot] = <factory>, candidates: list[JudgeTaskCandidate] = <factory>, missing_profiles: list[str] = <factory>, output_version_ref: str)[source]
One source record plus all judge candidates.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- id: str
- chunk_id: str
- source: str
- source_sha256: str
- applicable_termbase: list[ApplicableTermbaseEntrySnapshot]
- applicable_glossary: list[ApplicableGlossaryEntrySnapshot]
- candidates: list[JudgeTaskCandidate]
- output_version_ref: str
- class booktx.models.JudgeTask(*, version: Literal[1] = 1, judge_task_id: str, profile: str, source_profiles: list[str] = <factory>, source_language: str, target_language: str, target_locale: str = '', chapter_id: str = '', chapter_title: str = '', created_at: str, source_sha256: str, profile_config_sha256: str | None = None, source_config_sha256: str | None = None, context_view_sha256: str | None = None, context_view_path: str | None = None, mandatory_glossary_sha256: str | None = None, applicable_termbase_sha256: str | None = None, source_access: Literal['live', 'snapshot']='live', source_snapshot_sha256: str | None = None, source_snapshot_path: str | None = None, source_candidates_sha256: str | None = None, selection_purpose: Literal['compare', 'revise']='compare', records: list[JudgeTaskRecord] = <factory>)[source]
Durable task artifact for a cross-profile judge run.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- version: Literal[1]
- judge_task_id: str
- profile: str
- source_language: str
- target_language: str
- target_locale: str
- chapter_id: str
- chapter_title: str
- created_at: str
- source_sha256: str
- source_access: Literal['live', 'snapshot']
- selection_purpose: Literal['compare', 'revise']
- records: list[JudgeTaskRecord]
- class booktx.models.JudgeCandidateEvidence(*, label: str, profile: str, selected_kind: ~typing.Literal['translation', 'review'], selected_ref: str, version_ref: str | None = None, review_ref: str | None = None, target_sha256: str, validation_status: ~typing.Literal['ok', 'warning', 'error', 'missing'] = 'ok', findings: list[str] = <factory>)[source]
Persisted provenance for one candidate considered by the judge.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- label: str
- profile: str
- selected_kind: Literal['translation', 'review']
- selected_ref: str
- target_sha256: str
- validation_status: Literal['ok', 'warning', 'error', 'missing']
- class booktx.models.JudgeDecision(*, record_id: str, output_version_ref: str, output_target_sha256: str | None = None, decision_kind: ~typing.Literal['copy', 'edited'], selected_profile: str | None = None, selected_kind: ~typing.Literal['translation', 'review'] | None = None, selected_ref: str | None = None, selected_target_sha256: str | None = None, judge_task_id: str, judge_model: str = '', reason: str = '', candidate_evidence_sha256: str = '', candidate_evidence: list[~booktx.models.JudgeCandidateEvidence] = <factory>, created_at: str, updated_at: str)[source]
Persisted judge decision for one selected record.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- record_id: str
- output_version_ref: str
- decision_kind: Literal['copy', 'edited']
- selected_kind: Literal['translation', 'review'] | None
- judge_task_id: str
- judge_model: str
- reason: str
- candidate_evidence_sha256: str
- candidate_evidence: list[JudgeCandidateEvidence]
- created_at: str
- updated_at: str
- class booktx.models.TranslationSelectionLedger(*, version: Literal[1] = 1, profile: str, source_sha256: str, source_profiles: list[str] = <factory>, records: dict[str, ~booktx.models.JudgeDecision]=<factory>)[source]
Profile-local provenance ledger for a selection profile.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- version: Literal[1]
- profile: str
- source_sha256: str
- records: dict[str, JudgeDecision]
- class booktx.models.QualityReviewConfig(*, enabled: bool = False, active_passes: list[int] = <factory>, require_all_active_passes: bool = True, passes: list[ReviewPassConfig] = <factory>)[source]
Profile-level quality-review configuration.
Optional on
ProfileConfigso existingconfig.tomlfiles without a[quality_review]table round-trip unchanged.- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- enabled: bool
- require_all_active_passes: bool
- passes: list[ReviewPassConfig]
- class booktx.models.IndexesConfig(*, auto_export_after_insert: bool = False, auto_export_after_review: bool = False, write_jsonl: bool = True)[source]
Optional auto-export index configuration.
Stored under
[indexes]intranslations/<profile>/config.toml. WhenNone, no auto-export occurs.- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- auto_export_after_insert: bool
- auto_export_after_review: bool
- write_jsonl: bool
- class booktx.models.EpubOutputConfig(*, language_policy: Literal['target', 'source', 'preserve', 'explicit'] = 'target', language: str | None = None, hyphenation: Literal['auto', 'manual', 'none', 'preserve'] = 'auto', inject_css: bool = True, patch_body_language: bool = False)[source]
Optional EPUB output-language and hyphenation policy.
Stored under
[epub_output]in profile or legacy config.When
None(the default), booktx resolves effective policy from the profile kind: translation and legacy translation projects default totargetlanguage /autohyphenation; pass-through profiles default topreserve/preserve(byte-identical output). Storing the model opts the project into an explicit policy.See
booktx.epub_output_policyfor resolution and validation.- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- language_policy: Literal['target', 'source', 'preserve', 'explicit']
- hyphenation: Literal['auto', 'manual', 'none', 'preserve']
- inject_css: bool
- patch_body_language: bool
Persistent translation context for a booktx project.
The translation contract in booktx.models preserves JSON structure,
record ids, placeholders, tags, and protected names. It does not preserve
translation intent: style, world terminology, and user-specific decisions.
This module owns the profile-local machine-readable translation context
(translations/<profile>/context.json`) and the rendered human/agent view
(translations/<profile>/context.md`) for normal profile workflows.
context.json is authoritative; context.md is always regenerated from it.
The context is built deterministically and locally. It never calls an LLM, never
makes a network request, and never approves a glossary target on its own.
ready=false means translation must not begin; the user (or an agent driving
the CLI on the user’s behalf) must answer the required questions first.
- class booktx.context.StyleProfile(*, target_locale: str = 'de-DE', formality: Literal['informal', 'neutral', 'formal'] = 'neutral', register: str = '', prose_style: str = '', dialogue_style: str = '', sentence_policy: str = 'Prefer natural German prose; preserve meaning over word-for-word syntax.', punctuation_policy: str = '', units_policy: str = 'Keep source units unless the user says otherwise.')[source]
User-approved style decisions for the translation.
- model_config = {'extra': 'forbid', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- target_locale: str
- formality: Literal['informal', 'neutral', 'formal']
- register_level: str
- prose_style: str
- dialogue_style: str
- sentence_policy: str
- punctuation_policy: str
- units_policy: str
- class booktx.context.GlossaryEntry(*, source: str, source_variants: list[str] = <factory>, target: str | None = None, target_variants: list[str] = <factory>, require_target: bool = False, forbidden_targets: list[str] = <factory>, category: str = 'term', status: Literal['open', 'approved', 'rejected']='open', notes: str = '', examples: list[str] = <factory>, case_sensitive: bool = False, enforce: Literal['off', 'warn', 'error']='warn', origin: Literal['core', 'seed', 'agent_review', 'user', 'legacy', 'imported', 'source_analysis']='user', source_analysis_candidate_id: str | None = None)[source]
One glossary term with optional approved target and forbidden targets.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- source: str
- require_target: bool
- category: str
- status: Literal['open', 'approved', 'rejected']
- notes: str
- case_sensitive: bool
- enforce: Literal['off', 'warn', 'error']
- origin: Literal['core', 'seed', 'agent_review', 'user', 'legacy', 'imported', 'source_analysis']
- class booktx.context.ContextQuestion(*, id: str, topic: str, question: str, answer: str | None = None, status: Literal['open', 'recommended', 'answered', 'skipped']='open', required: bool = True, origin: Literal['core', 'seed', 'agent_review', 'user', 'legacy', 'source_analysis']='core', source_analysis_candidate_ids: list[str] = <factory>, recommendation: str | None = None, recommendation_reason: str = '', recommendation_source: str = '', answer_source: Literal['user', 'imported', 'legacy', 'forced', 'agent'] | None=None, approved_by: str = '', approved_at: str = '')[source]
One question that may need a user answer before translation can begin.
requiredquestions gate readiness: a context cannot be marked ready while any required question is still open.- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- id: str
- topic: str
- question: str
- status: Literal['open', 'recommended', 'answered', 'skipped']
- required: bool
- origin: Literal['core', 'seed', 'agent_review', 'user', 'legacy', 'source_analysis']
- recommendation_reason: str
- recommendation_source: str
- answer_source: Literal['user', 'imported', 'legacy', 'forced', 'agent'] | None
- approved_by: str
- approved_at: str
- class booktx.context.ChapterContext(*, chapter_id: str, title: str = '', chunk_ids: list[str] = <factory>, source_summary: str = '', translation_summary: str = '', decisions_added: list[str] = <factory>, open_issues: list[str] = <factory>)[source]
Per-chapter notes appended as the agent completes chapters.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- chapter_id: str
- title: str
- source_summary: str
- translation_summary: str
- class booktx.context.TranslationContext(*, version: int = 1, source_language: str, target_language: str, source_title: str = '', source_author: str = '', source_sha256: str = '', ready: bool = False, ready_forced: bool = False, ready_reason: str = '', ready_by: str = '', ready_at: str = '', style: StyleProfile = <factory>, global_rules: list[str] = <factory>, glossary: list[GlossaryEntry] = <factory>, questions: list[ContextQuestion] = <factory>, chapter_contexts: list[ChapterContext] = <factory>)[source]
The authoritative translation context for one project.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- version: int
- source_language: str
- target_language: str
- source_title: str
- source_author: str
- source_sha256: str
- ready: bool
- ready_forced: bool
- ready_reason: str
- ready_by: str
- ready_at: str
- style: StyleProfile
- glossary: list[GlossaryEntry]
- questions: list[ContextQuestion]
- chapter_contexts: list[ChapterContext]
- booktx.context.apply_answer_to_context(context: TranslationContext, question_id: str, text: str) None[source]
- booktx.context.context_path(project: Project) Path[source]
Path to the authoritative context JSON for the selected translation scope.
- booktx.context.context_markdown_path(project: Project) Path[source]
Path to the rendered context markdown for the selected translation scope.
- booktx.context.chapter_map_path(project: Project) Path[source]
Path to
.booktx/chapter-map.json(owned bybooktx.chapters).
- booktx.context.load_context(project: Project) TranslationContext | None[source]
Load the context, or return
Noneif no context file exists.A corrupt context raises a
ValueErrorso the caller can surface it rather than silently dropping the user’s decisions.
- booktx.context.write_context(project: Project, context: TranslationContext) None[source]
Persist
contextto the resolved profile’scontext.json.
- booktx.context.default_context(project: Project, source_sha256: str = '') TranslationContext[source]
Build a not-ready context pre-filled from the project config.
Source/target languages and the source digest are taken from the project. Style and glossary start empty/seeded; required questions are open.
- booktx.context.baseline_payload(context: TranslationContext) dict[str, object][source]
Return the semantic baseline payload for version resolution.
- booktx.context.baseline_sha256(context: TranslationContext) str[source]
Return the baseline hash excluding chronological chapter notes.
- booktx.context.chapter_notes_before_target(chapter_map: ChapterMap, notes: list[ChapterContext], target_chapter_id: str) list[ChapterContext][source]
Return prior chapter notes in chapter-map order for one target chapter.
- booktx.context.context_history_dir(project: Project) Path[source]
Return the history directory adjacent to the live context files.
- booktx.context.context_history_views_dir(project: Project) Path[source]
Return the directory containing immutable context view snapshots.
- booktx.context.ensure_context_view_snapshot(project: Project, *, baseline_ref: str, baseline_sha256: str, target_chapter_id: str, notes_scope: str = 'before_target_chapter') ContextViewSnapshot[source]
Compose and persist an immutable task context view snapshot.
- booktx.context.render_context_markdown(context: TranslationContext, *, view: Literal['full', 'effective', 'provenance'] = 'full') str[source]
Render a short, agent-readable markdown view of
context.The markdown is always derived from
context; it is never authoritative.
- booktx.context.write_context_markdown(project: Project, context: TranslationContext) None[source]
Render
contextto the resolved profile’scontext.md.
- booktx.context.parse_context_markdown_chapter_notes(markdown: str) list[ChapterContext][source]
Parse the rendered
## Chapter notessection fromcontext.md.Only the
## Chapter notessection is parsed; parsing stops at the next level-2 heading. Chapter headings must match the rendered shape### 0006or### 0006 <separator> Titlewhere<separator>is an em dash, en dash, or ASCII hyphen. The four bullet prefixes- Source summary:,- Translation summary:,- Decision:, and- Open issue:are parsed exactly. Unknown non-empty content inside a chapter note raisesValueError; nothing is silently mapped to open issues.
- booktx.context.chapter_contexts_equivalent(left: ChapterContext, right: ChapterContext) bool[source]
Return True when two chapter notes have the same durable content.
chunk_idsis ignored because rendered Markdown does not include chunk ids; they are hydrated fromchapter-map.jsonon import or upsert.
- class booktx.context.ContextMarkdownDrift(*, missing_in_json: list[str] = <factory>, conflicting: list[str] = <factory>, parse_errors: list[str] = <factory>)[source]
Drift between rendered
context.mdchapter notes andcontext.json.- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- property unsafe_to_overwrite: bool
True when overwriting
context.mdwould discard Markdown-only notes.
- booktx.context.analyze_context_markdown_drift(project: Project, context: TranslationContext) ContextMarkdownDrift[source]
Compare existing
context.mdchapter notes withcontext.json.If
context.mdis missing, no drift is reported. Parser failures populateparse_errorsand make the overwrite unsafe.
- booktx.context.hydrate_chapter_contexts_from_chapter_map(project: Project, chapters: list[ChapterContext]) None[source]
Fill
titleandchunk_idsfromchapter-map.jsonwhere absent.Existing non-empty titles or chunk ids are never overwritten.
- booktx.context.merge_chapter_contexts(context: TranslationContext, imported: list[ChapterContext], *, replace_existing: bool = False, append_existing_lists: bool = False) list[str][source]
Merge imported chapter notes into
contextand return changed ids.Default behavior adds notes whose chapter ids are absent, treats equivalent notes as no-ops, and refuses differing existing notes. Pass
replace_existingto replace durable fields (preserving or hydratingchunk_ids), orappend_existing_liststo keep existing summaries unless empty and append non-duplicate decisions and open issues in order. The two modes are mutually exclusive.
- booktx.context.upsert_chapter_context(context: TranslationContext, note: ChapterContext, *, replace_decisions: bool = False, replace_open_issues: bool = False, replace_all: bool = False) None[source]
Create or update one chapter note.
Title and summaries update only when provided. Decisions and open issues append by default (avoiding exact duplicates) and replace only when the matching replace flag is true.
When
replace_allis true, the stored note is set exactly to the supplied values (empty strings and empty lists are allowed).
- booktx.context.ensure_context_markdown_safe_to_overwrite(project: Project, context: TranslationContext, *, allow_discard_md_only: bool = False) None[source]
Raise
ValueErrorif writingcontext.mdwould discard notes.Runs drift analysis against the existing
context.md. Pass “allow_discard_md_only=True`` for commands whose purpose is to overwrite Markdown despite unsafe drift.
- booktx.context.seed_questions() list[ContextQuestion][source]
Generic initial questionnaire for any translation project.
The questions marked
required=Truegateready. Typography and units are left optional: they affect style but a missing answer does not block the terminology safety that the context gate exists to provide.
- booktx.context.seed_glossary() list[GlossaryEntry][source]
Generic glossary seeds (empty by default).
Book-specific seeds can be loaded via the
--seedoption onbooktx context init. The Shadows-of-Apt template is available as--seed shadows-of-apt.
- booktx.context.unresolved_required_questions(context: TranslationContext) list[ContextQuestion][source]
- booktx.context.unapproved_required_questions(context: TranslationContext) list[ContextQuestion][source]
- booktx.context.next_question_id(context: TranslationContext, prefix: str = 'Q') str[source]
Sentence segmentation and chunk packing.
The extractor hands chunking a list of protected prose spans (each already had names and inline tags replaced by placeholder tokens). chunking:
Segments each span into sentences with
phrasplit.Assigns each resulting sentence a stable record id.
Packs records into
Chunkobjects of at mostchunk_sizerecords, numbering chunks from 1.
Record ids are NNNN-NNNNNN: the 4-digit chunk id, a dash, and a 1-based
6-digit index inside that chunk. Chunk ids are zero-padded 4-digit strings.
The goal stated in the spec is one source sentence to one translated sentence — chunking never merges or splits beyond what phrasplit returns.
- class booktx.chunking.ProseSpan(text: str, placeholders: list[Placeholder], protected_terms: list[str], presegmented: bool = False, source_markup: Literal['plain:v1', 'epub-inline-xhtml:v1'] = 'plain:v1')[source]
A protected prose span produced by a format extractor.
textis the prose or inline-XHTML fragment with names already replaced by placeholder tokens.placeholderslists every placeholder that appears anywhere intext. Segmentation filters that list per record so each record carries only placeholders visible in its own source.protected_termsis the subset of names relevant to this span.presegmentedmeans the upstream extractor already selected this span as one sentence/record. In that case booktx must not run phrasplit again.source_markuprecords the inline markup contract of the span so it can propagate intoRecord.source_markupfor defense-in-depth record-level validation. The EPUB span manifest remains the authority at build time.- text: str
- placeholders: list[Placeholder]
- presegmented: bool
- source_markup: Literal['plain:v1', 'epub-inline-xhtml:v1']
- booktx.chunking.segmenter_metadata(language: str) dict[str, object][source]
Return deterministic metadata describing the extraction segmenter.
- booktx.chunking.segment_spans(spans: list[ProseSpan], *, language: str = 'en') list[Record][source]
Segment every span into one
Recordper sentence.Empty/whitespace-only sentences are dropped so a span never yields a blank record. Each record carries only placeholders and protected terms visible in that record’s source text.
- booktx.chunking.pack_chunks(records: list[Record], *, source_language: str, target_language: str = '', chunk_size: int = 50) list[Chunk][source]
Pack records into chunks of at most
chunk_sizeand assign final ids.Final record ids are
NNNN-NNNNNN(chunk id + 1-based intra-chunk index).
- booktx.chunking.spans_to_chunks(spans: list[ProseSpan], *, source_language: str, target_language: str = '', chunk_size: int = 50) list[Chunk][source]
Convenience: segment spans then pack into chunks.
Placeholder protection and restoration for names and inline non-translatable spans.
booktx never sends raw protected names or inline markup to the translating agent. Instead it replaces them with stable, collision-free tokens before segmentation, and restores the originals verbatim after the agent returns the translated text.
Two placeholder kinds are used (see booktx_coding_agent_start.md):
__NAME_NNN__— a manually protected term fromnames.json(e.g.Alice->__NAME_001__).__TAG_NNN__— an inline non-translatable span extracted from the document (inline code, URLs/link destinations) ->__TAG_001__.
Tokens are unique within a document and round-trip safe: applying
protect_names() then restore() (or protect_tags() then
restore()) returns the original text exactly.
- booktx.placeholders.TRANSLATABLE_INLINE_PARENTS = {'blockquote', 'em', 'heading', 'list_item', 'paragraph', 'strong', 'table_cell', 'td', 'th'}
Translatable container types whose
inlinechildren we extract from.
- booktx.placeholders.SKIP_BLOCK_TYPES = {'code_block', 'fence', 'html_block'}
Block types whose content must never be translated.
- class booktx.placeholders.ProtectResult(text: str, placeholders: list[Placeholder])[source]
Outcome of a protect pass.
texthas originals replaced by tokens.placeholdersis ordered by first appearance and eachPlaceholder.originalis the verbatim string to restore.- text: str
- placeholders: list[Placeholder]
- __init__(text: str, placeholders: list[Placeholder]) None
- booktx.placeholders.protect_names(text: str, terms: list[str], *, start_index: int = 1) ProtectResult[source]
Replace each protected term in
textwith a__NAME_NNN__token.Matching is case-sensitive and whole-term (no word-boundary tricks) so multi-word names like
Mr. Smithwin overMr.. Tokens are numbered by first appearance in the text (longest terms are matched first to avoid sub-string collisions, but the token id follows text order).
- booktx.placeholders.protect_tags(text: str, originals: list[str], *, start_index: int = 1) ProtectResult[source]
Replace each inline non-translatable span with a
__TAG_NNN__token.originalsis the list of verbatim spans to hide (inline code, URLs, …) as discovered by the format extractor. They are replaced longest-first and only if the span actually occurs intext.
- booktx.placeholders.restore(text: str, placeholders: list[Placeholder]) str[source]
Replace each token in
textwith its recorded original.Used by the build step on the agent’s translated text. Tokens are restored verbatim; missing tokens are left in place (the validator flags those).
- booktx.placeholders.span_token_ids(template: str) list[str][source]
Return the
__SPANTX_NNNN__tokens found intemplate, in order.
Markdown extraction and rebuild.
Extraction walks a markdown document with markdown_it, identifies the
translatable prose spans (the inline tokens inside paragraphs, headings,
list items, blockquotes, and table cells), hides inline code / link URLs / raw
HTML behind __TAG_NNN__ tokens, hides protected names behind
__NAME_NNN__ tokens, and returns:
a template — the original markdown with each translatable inline replaced by a
__SPANTX_NNNN__placeholder; anda list of
ProseSpanin placeholder order.
Non-translatable blocks (YAML front matter, fenced code, indented code, HTML blocks) are left untouched in the template.
Rebuild is the inverse: substitute each __SPANTX_NNNN__ with the supplied
translated span text (already name/tag-restored by build.py).
- class booktx.markdown_io.MarkdownExtraction(template: str, spans: list[ProseSpan], front_matter: str)[source]
Result of
extract_markdown().- template: str
- front_matter: str
- booktx.markdown_io.FRONT_MATTER_RE = re.compile('\\A---\\r?\\n(.*?\\r?\\n)---\\r?\\n?', re.DOTALL)
YAML front matter at the very start of a document.
- booktx.markdown_io.extract_markdown(text: str, *, protected_terms: list[str] | None = None) MarkdownExtraction[source]
Extract translatable spans from
text.Returns the rebuild template and the ordered prose spans.
- booktx.markdown_io.build_markdown(template: str, span_replacements: list[str]) str[source]
Rebuild markdown by substituting each
__SPANTX_NNNN__in order.span_replacements[i]replaces the(i+1)-th span placeholder. Extra replacements are ignored; missing replacements leave the token in place.
- booktx.markdown_io.split_front_matter(text: str) tuple[str, str][source]
Split leading YAML front matter from the body.
Returns
(front_matter_with_fences, body). If there is no front matter,front_matteris empty andbodyis the original text.
EPUB extraction and rebuild adapters over epub2text and text2epub.
- class booktx.epub_io.EpubExtraction(spans: list[ProseSpan] = <factory>, span_refs: list[EpubSpanRef] = <factory>, text2epub_manifest: dict[str, object]=<factory>, source_sha256: str = '', navigation: list[EpubNavigationRef] = <factory>)[source]
Structured EPUB extraction data used by booktx.
- span_refs: list[EpubSpanRef]
- source_sha256: str
- __init__(spans: list[ProseSpan] = <factory>, span_refs: list[EpubSpanRef] = <factory>, text2epub_manifest: dict[str, object]=<factory>, source_sha256: str = '', navigation: list[EpubNavigationRef] = <factory>) None
- booktx.epub_io.build_epub(source_path: str, output_path: str, extraction: EpubExtraction, span_replacements: list[str]) str[source]
Rebuild an EPUB via text2epub using one replacement per extracted span.
span_replacementsprovides one target string per entry inextraction.spans(sentence-level). Spans are grouped back into onetext2epub.Replacementper block-levelspan_ref: for eachspan_refthe replacements whose span index falls in[span_ref.span_index, next_span_index)are joined with a space. When every span of a block is unchanged, the original raw block is reused so pass-through builds stay byte-identical even when the joined sentence XHTML differs from the stored block fragment.
- booktx.epub_io.extract_epub(path: str, *, protected_terms: list[str] | None = None) EpubExtraction[source]
Extract translatable EPUB spans through epub2text structured blocks.
- booktx.epub_io.read_epub(path: str) ZipFile[source]
Open an EPUB archive for direct ZIP-level inspection.
Helpers for the migrated EPUB extraction manifest.
- booktx.epub_manifest.assert_source_sha(path: Path | str, expected_sha: str) None[source]
Raise if
pathdoes not matchexpected_sha.
- booktx.epub_manifest.build_raw_block_index(structured: Any) dict[str, _RawBlock][source]
Map epub2text block ids to raw archive offsets and fragments.
- booktx.epub_manifest.load_epub_template_from_manifest(manifest: Manifest) EpubTemplateData[source]
Validate and return the EPUB v2 template stored in
manifest.
- booktx.epub_manifest.sha256_path(path: Path | str) str[source]
Return the SHA256 hex digest of
path.
Convert epub2text navigation entries into stored manifest refs.
- booktx.epub_manifest.structured_to_span_refs(structured: Any, *, protected_terms: list[str], raw_block_index: dict[str, _RawBlock] | None = None) tuple[list[ProseSpan], list[EpubSpanRef]][source]
Build booktx spans plus ordered span refs from epub2text blocks.
- booktx.epub_manifest.structured_to_text2epub_manifest(structured: Any, *, raw_block_index: dict[str, _RawBlock] | None = None) dict[str, object][source]
Convert an epub2text structured extraction to a text2epub manifest.
Chapter detection and chapter-map persistence for booktx.
- class booktx.chapters.Chapter(*, chapter_id: str, title: str = '', chunk_ids: list[str] = <factory>, start_record_id: str = '', end_record_id: str = '', record_count: int = 0)[source]
One detected chapter and the chunk range it covers.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- chapter_id: str
- title: str
- start_record_id: str
- end_record_id: str
- record_count: int
- class booktx.chapters.ChapterMap(*, version: int = 2, source_sha256: str = '', chapters: list[Chapter] = <factory>)[source]
Detected chapters for a project.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- version: int
- source_sha256: str
- booktx.chapters.detect_chapters(project: Project) ChapterMap[source]
Detect chapters and write no files.
- booktx.chapters.load_chapter_map(project: Project) ChapterMap | None[source]
- booktx.chapters.write_chapter_map(project: Project, chapter_map: ChapterMap) None[source]
Validation of agent-translated chunks against the booktx contract.
The validator loads every source chunk in .booktx/chunks/ and the matching
translated chunk in .booktx/translated/ (if present), and enforces the
hard rules from booktx_coding_agent_start.md:
The translated JSON must be valid JSON.
The record count must be unchanged.
No record id may change.
No target may be empty.
No placeholder may be removed or changed.
No protected name may be translated or removed.
The translated file must contain no commentary outside the JSON structure.
The goal is one source sentence to one translated sentence. The validator never merges or splits records.
A ValidationReport collects per-chunk findings and a summary, and is
written to .booktx/reports/. validate_project returns the report and
exits non-zero on any mandatory failure.
- class booktx.validate.Severity[source]
Finding severity labels.
- INFO = 'info'
- WARN = 'warn'
- ERROR = 'error'
- class booktx.validate.Finding(chunk_id: str, severity: str, rule: str, message: str, record_id: str = '', record_ids: list[str] = <factory>, chapter_id: str = '', chapter_title: str = '', span_index: int | None = None, block_id: str = '', document_href: str = '', source: str = '', target: str = '', candidate_kind: str = '', candidate_ref: str = '', candidate_scope: str = '')[source]
One validation finding for one chunk.
Optional location/context fields are populated from EPUB inline-XHTML preflight findings and left empty for plain record-level findings. They are included in
as_dict()only when non-empty so the JSON report stays backward compatible and readable.- chunk_id: str
- severity: str
- rule: str
- message: str
- record_id: str
- chapter_id: str
- chapter_title: str
- block_id: str
- document_href: str
- source: str
- target: str
- candidate_kind: str
- candidate_ref: str
- candidate_scope: str
- __init__(chunk_id: str, severity: str, rule: str, message: str, record_id: str = '', record_ids: list[str] = <factory>, chapter_id: str = '', chapter_title: str = '', span_index: int | None = None, block_id: str = '', document_href: str = '', source: str = '', target: str = '', candidate_kind: str = '', candidate_ref: str = '', candidate_scope: str = '') None
- class booktx.validate.ValidationReport(project: str, profile: str = '', target_language: str = '', target_locale: str = '', findings: list[Finding] = <factory>, chunks_checked: int = 0, chunks_passed: int = 0, chunks_missing_translation: int = 0, generated_at: str = '')[source]
Aggregated validation result for a project.
- project: str
- profile: str
- target_language: str
- target_locale: str
- chunks_checked: int
- chunks_passed: int
- chunks_missing_translation: int
- generated_at: str
- property passed: bool
- property blocking_findings: list[Finding]
Findings that affect pass/fail regardless of history mode.
Effective-output and structural findings always count. Inactive historical content findings are excluded: they describe candidates that are not the current output and must not fail a normal build.
- class booktx.validate.EffectiveTranslations(chunks: dict[str, ~booktx.models.TranslatedChunk]=<factory>, findings: list[Finding] = <factory>)[source]
Merged accepted translations from the store and valid legacy chunks.
- chunks: dict[str, TranslatedChunk]
- booktx.validate.load_validation_context(project: Project, *, context_view_path: str | None = None) TranslationContext | None[source]
Load the context that should be used for validation.
- booktx.validate.strict_load_translated(path: Path) tuple[TranslatedChunk | None, str | None][source]
Load a translated chunk, detecting commentary outside the JSON.
Returns
(model_or_None, error_message_or_None). We parse twice: once permissively into the model, once strictly to catch trailing commentary.
- booktx.validate.validate_record_pair(source_rec: Record, target_rec: TranslatedRecord, chunk_id: str, context: TranslationContext | None = None) list[Finding][source]
Validate one translated record against one source record.
- booktx.validate.load_effective_translated_chunks(project: Project, *, source_chunks: dict[str, Chunk] | None = None, context: TranslationContext | None = None, include_inactive_versions: bool = False, all_versions_strict: bool = False) EffectiveTranslations[source]
Merge valid legacy chunk files and accepted store records.
- booktx.validate.validate_project(project: Project, *, include_inactive_versions: bool = False, all_versions_strict: bool = False, chapter_id: str | None = None, record_ids: set[str] | None = None, task_id: str | None = None, require_complete: bool = False) ValidationReport[source]
Validate every translated chunk in
project.Missing translations are not errors; only present-but-invalid translated files produce error findings. Stale translated files whose chunk id no longer exists produce a warning.
- booktx.validate.validate_chunk_pair(source: Chunk, translated_path: Path | None, context: TranslationContext | None = None) list[Finding][source]
Validate one source chunk against its translated file (if any).
- booktx.validate.review_coverage_findings(stored: StoredTranslationRecordV2, quality_cfg: QualityReviewConfig, chunk_id: str, record_id: str, *, force_error: bool = False) list[Finding][source]
Per-pass review-coverage findings for one record.
force_error(used bybuild --require-reviewed) treats every coverage gap as an ERROR regardless of the passenforcesetting. Otherwise gaps are emitted only whenenforceiswarnorerrorand the severity followsenforce.
- booktx.validate.write_report(project: Project, report: ValidationReport) Path[source]
Persist the validation report to
.booktx/reports/and return it.
Rebuild the final translated document from validated translated chunks.
- class booktx.build.BuildResult(*, output_path: Path, format: str, span_count: int, report: dict[str, object] | None = None)[source]
Outcome of a build run.
- exception booktx.build.BuildError[source]
User-facing error during build.
- booktx.build.build_project(project: Project, *, require_complete: bool = False, require_reviewed: bool = False) BuildResult[source]
Build the translated output document for
project.
- booktx.build.records_to_span_text(span: ProseSpan, targets: list[str]) str[source]
Join translated record targets back into one span string.
Typed project status snapshot and runtime index.
This module owns the record/word/chunk/chapter aggregation that used to live
as the private _project_status_snapshot dict inside booktx.cli. It
returns a typed StatusSnapshot (which serializes to the stable
status --json v1 shape) plus a StatusRuntimeIndex dataclass that
carries the lookup maps the command layer still needs for task creation and
record acceptance.
Design notes:
The public JSON shape is preserved exactly, including the nested
record_range: {start, end}field on each chapter. Do not flatten it tostart_record_id/end_record_idwithout intentionally versioning to v2.The snapshot model has only public fields; no
_privatekeys leak into JSON output. Runtime lookup maps live on the separateStatusRuntimeIndexso they never get serialized.This module is intentionally free of Typer/Rich imports so it can be unit tested directly. CLI-specific error UX (
_die) stays incli.py; the caller computescontext_exists/context_readyand passes them in.
- class booktx.status.RecordRange(*, start: str, end: str)[source]
Inclusive record-id range covered by a chapter (nested in JSON v1).
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- start: str
- end: str
- class booktx.status.ChapterProgress(*, chapter_id: str, title: str, chunk_ids: list[str] = <factory>, pending_chunk_ids: list[str] = <factory>, record_range: RecordRange, records_total: int, records_translated: int, records_remaining: int, source_words_total: int, source_words_translated: int, source_words_remaining: int, status: str)[source]
Per-chapter translation coverage (matches the
status --jsonv1 shape).- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- chapter_id: str
- title: str
- record_range: RecordRange
- records_total: int
- records_translated: int
- records_remaining: int
- source_words_total: int
- source_words_translated: int
- source_words_remaining: int
- status: str
- class booktx.status.ChunkProgress(*, chunk_id: str, records_total: int, records_translated: int, records_remaining: int, source_words_total: int, source_words_translated: int, source_words_remaining: int, status: str)[source]
Per-chunk translation coverage (matches the
status --jsonv1 shape).- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- chunk_id: str
- records_total: int
- records_translated: int
- records_remaining: int
- source_words_total: int
- source_words_translated: int
- source_words_remaining: int
- status: str
- class booktx.status.SourceStatus(*, filename: str, format: str, source_language: str, target_language: str, source_sha256: str, source_drifted: bool = False)[source]
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- filename: str
- format: str
- source_language: str
- target_language: str
- source_sha256: str
- source_drifted: bool
- class booktx.status.ContextStatus(*, exists: bool, ready: bool)[source]
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- exists: bool
- ready: bool
- class booktx.status.StatusTotals(*, source_words: int = 0, translated_words: int = 0, remaining_words: int = 0, records_total: int = 0, records_translated: int = 0, records_remaining: int = 0, chunks_total: int = 0, chunks_complete: int = 0, chunks_partial: int = 0, chunks_pending: int = 0, chapters_total: int = 0, chapters_complete: int = 0, chapters_partial: int = 0, chapters_pending: int = 0, invalid_translation_files: int = 0, stale_translation_files: int = 0)[source]
Aggregate translation coverage totals.
Used by
status --jsonand persisted inTranslationTodo.start_totals. Keep this model inmodels.pyso durable todo validation never depends on a late Pydantic forward-reference rebuild.- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- source_words: int
- translated_words: int
- remaining_words: int
- records_total: int
- records_translated: int
- records_remaining: int
- chunks_total: int
- chunks_complete: int
- chunks_partial: int
- chunks_pending: int
- chapters_total: int
- chapters_complete: int
- chapters_partial: int
- chapters_pending: int
- invalid_translation_files: int
- stale_translation_files: int
- class booktx.status.VersionCoverage(*, version_ref: str, version: int, subversion: int, records_with_candidate: int = 0, active_records: int = 0)[source]
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- version_ref: str
- version: int
- subversion: int
- records_with_candidate: int
- active_records: int
- class booktx.status.TrackCoverage(*, version: int, label: str | None = None, records_with_candidate: int = 0, active_records: int = 0, latest_subversion: int | None = None)[source]
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- version: int
- records_with_candidate: int
- active_records: int
- class booktx.status.StatusSnapshot(*, version: int = 1, project: str, source: SourceStatus, context: ContextStatus, totals: StatusTotals, next: ChapterProgress | None = None, chapters: list[ChapterProgress] = <factory>, version_coverage: list[VersionCoverage] = <factory>, track_coverage: list[TrackCoverage] = <factory>)[source]
Typed project status. Serializes to the
status --jsonv1 payload.- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- version: int
- project: str
- source: SourceStatus
- context: ContextStatus
- totals: StatusTotals
- next: ChapterProgress | None
- chapters: list[ChapterProgress]
- version_coverage: list[VersionCoverage]
- track_coverage: list[TrackCoverage]
- class booktx.status.StatusRuntimeIndex(source_chunks: dict[str, Chunk], source_by_id: dict[str, SourceRecordView], translated_by_id: dict[str, TranslatedRecord], record_ids_by_chapter: dict[str, list[str]], record_to_chapter: dict[str, str], chapters_by_id: dict[str, ChapterProgress], chapter_summaries: list[ChapterProgress], chunk_summaries: list[ChunkProgress], record_error_by_id: dict[str, Finding])[source]
Lookup maps derived while building a
StatusSnapshot.These are intentionally not serialized. They carry the live objects the command layer needs for task creation, record acceptance, and selection.
- translated_by_id: dict[str, TranslatedRecord]
- chapters_by_id: dict[str, ChapterProgress]
- chapter_summaries: list[ChapterProgress]
- chunk_summaries: list[ChunkProgress]
- __init__(source_chunks: dict[str, Chunk], source_by_id: dict[str, SourceRecordView], translated_by_id: dict[str, TranslatedRecord], record_ids_by_chapter: dict[str, list[str]], record_to_chapter: dict[str, str], chapters_by_id: dict[str, ChapterProgress], chapter_summaries: list[ChapterProgress], chunk_summaries: list[ChunkProgress], record_error_by_id: dict[str, Finding]) None
- class booktx.status.StatusBundle(snapshot: StatusSnapshot, index: StatusRuntimeIndex, epub_audit: EpubAuditSummary | None = None)[source]
A status snapshot paired with its runtime index.
- snapshot: StatusSnapshot
- index: StatusRuntimeIndex
- epub_audit: EpubAuditSummary | None
- __init__(snapshot: StatusSnapshot, index: StatusRuntimeIndex, epub_audit: EpubAuditSummary | None = None) None
- class booktx.status.EpubAuditSummary(*, error_count: int = 0, warning_count: int = 0, has_blocking_errors: bool = False, findings: list[EpubAuditFindingSummary] = <factory>)[source]
Recomputed EPUB visible-TOC chapter-audit summary for the current source.
Always recomputed at status/gate time; the persisted report is user-facing only and is never trusted for workflow gating.
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- error_count: int
- warning_count: int
- has_blocking_errors: bool
- findings: list[EpubAuditFindingSummary]
- class booktx.status.ProfileOverview(*, profile: str, kind: str = 'translation', target_language: str, target_locale: str = '', model: str = '', path: str, translated_records: int = 0, total_records: int = 0)[source]
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- profile: str
- kind: str
- target_language: str
- target_locale: str
- model: str
- path: str
- translated_records: int
- total_records: int
- class booktx.status.ProfilesOverview(*, project: str, source: str = '', source_records: int = 0, profiles: list[ProfileOverview] = <factory>)[source]
- model_config = {'extra': 'forbid'}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- project: str
- source: str
- source_records: int
- profiles: list[ProfileOverview]
- booktx.status.coverage_status(*, total: int, translated: int, has_error: bool) str[source]
Return the coverage status label for a chunk or chapter.
- booktx.status.build_status_snapshot(proj: Project, *, context_exists: bool, context_ready: bool) StatusBundle[source]
Build a typed status snapshot and runtime index for
proj.context_exists/context_readyare computed by the caller (the CLI owns the invalid-context error UX). Everything else — source/chunk loading, effective-translation merge, chapter mapping, coverage math — is owned here.
- booktx.status.build_profiles_overview(project: Project) ProfilesOverview[source]
- booktx.status.selected_chapter(bundle: StatusBundle, chapter_id: str | None) ChapterProgress | None[source]
Return the focused chapter for the
status/nextcommands.chapter_id=Noneselects the first chapter with remaining records (snapshot.next), which isNonewhen everything is translated. A specificchapter_idreturns that chapter whether or not it still has remaining records; the caller decides how to react to a complete chapter. ReturnsNoneonly when the id is unknown.
Translation-task creation, durable task paths, and submission hints.
Centralizes the durable-file layout and id derivation for translation tasks so the command layer stops reconstructing paths and submit-hints inline.
Profile-layout projects (primary):
translations/<profile>/tasks/<id>.json translations/<profile>/tasks/<id>.source.block.txt translations/<profile>/ingest/<id>.json translations/<profile>/ingest/<id>.block.txt
Legacy single-layout projects (compatibility only):
.booktx/tasks/<id>.json .booktx/tasks/<id>.source.block.txt .booktx/ingest/<id>.json .booktx/ingest/<id>.block.txt
All task-path access goes through translation_task_dir(project) which
enforces the profile-required guard for source-only projects. The
TaskPaths value object bundles the four per-task files and renders the
project-relative display strings and submit commands the CLI prints.
- class booktx.tasks.TaskPaths(task_json: Path, source_block: Path, ingest_json: Path, ingest_block: Path)[source]
The four durable files owned by one translation task.
- task_json: Path
- source_block: Path
- ingest_json: Path
- ingest_block: Path
- booktx.tasks.make_task_id(chapter_id: str, first_record_id: str, record_ids: list[str]) str[source]
Derive a deterministic, path-safe task id.
Uses a stable
blake2sdigest (digest_size=4) of the joined record ids instead of Python’s process-randomizedhash(), plus a seconds-precision UTC timestamp so same-day collisions are extremely unlikely.
- booktx.tasks.task_paths(project: Project, task_id: str) TaskPaths[source]
Return the
TaskPathsbundle fortask_id.Routes through
translation_task_dir(project)so a source-only project (no selected profile) hits the profile-required guard instead of silently assuming legacy.booktx/taskspaths.
- booktx.tasks.project_relative(path: Path, root: Path) str[source]
Return a stable project-relative display path when possible.
- booktx.tasks.limit_records_by_words(record_ids: list[str], source_by_id: Mapping[str, SourceRecordView], max_words: int) list[str][source]
Return the longest prefix of
record_idswithinmax_words.The first record is always included when
record_idsis non-empty so a single long record still makes progress.
- booktx.tasks.select_translation_record_ids(bundle: StatusBundle, chapter: ChapterProgress, *, unit: str, max_words: int) tuple[str, list[str]][source]
Select the record ids for the next translation task within
chapter.
- booktx.tasks.create_translation_task(project: Project, bundle: StatusBundle, chapter: ChapterProgress, *, mode: RuntimeMode | None = None, unit: str, record_ids: list[str], requested_max_words: int | None = None, todo_id: str | None = None) TranslationTask[source]
Build, persist, and render durable files for one translation task.
- booktx.tasks.write_ingest_template(project: Project, task: TranslationTask) Path[source]
Create the durable JSON submission file for a task without overwriting work.
- booktx.tasks.write_block_ingest_template(project: Project, task: TranslationTask, *, mode: RuntimeMode | None = None) Path[source]
Create the durable block submission file for a task without overwriting work.
The file starts with metadata comment headers (ignored by the block parser) followed by one
>>> RECORD_IDheader per record. The agent fills in the target text under each header.
- booktx.tasks.write_task_source_block(project: Project, task: TranslationTask) Path[source]
Create the durable source-view file for a task without overwriting work.
Holds the original source text for each record in the task so a coding agent can translate against a stable file instead of a large stdout dump.
Submission payload parsing for the translate-accept commands.
Owns the three submission wire formats (JSON, TSV, block) and the input-source
selection that the translate insert command used to inline. Parsers return
booktx.acceptance.SubmittedRecord lists directly so the acceptance
service never sees raw dicts.
Validation errors are raised as booktx.config.BooktxError; the CLI
renders them via the shared _handle_booktx_error path, so messages stay
identical to the previous inline _die calls.
- class booktx.submissions.ParsedSubmission(records: list[SubmittedRecord], task_id: str | None = None, translation_version: str | None = None, profile: str | None = None)[source]
A parsed submission payload: optional task id + validated records.
- __init__(records: list[SubmittedRecord], task_id: str | None = None, translation_version: str | None = None, profile: str | None = None) None[source]
- records
- task_id
- translation_version
- profile
- booktx.submissions.parse_json_submission(text: str) ParsedSubmission[source]
Parse a JSON
{"task_id": ..., "records": [{"id","target"}, ...]}payload.
- booktx.submissions.parse_tsv_submission(text: str) ParsedSubmission[source]
Parse
<record-id>\t<target>lines (blank lines ignored).
- booktx.submissions.parse_block_submission(text: str) ParsedSubmission[source]
Parse the durable
>>> <record-id>block format.
- booktx.submissions.read_submission_file(path: Path, *, ingest_hint: str | None = None) str[source]
Read a submission file, raising BooktxError (never a traceback) on failure.
ingest_hintis a project-relative path to the canonical profile-local ingest file (e.g.translations/<profile>/ingest/<task>.block.txt). When the requested file is missing and differs from the hint, the error message points the agent at the generated ingest location instead of/tmpor other temporary paths.
- booktx.submissions.resolve_submission(*, record_id: str | None, target: str | None, input_format: str, stdin: bool, json_file: Path | None, input_file: Path | None, ingest_hint: str | None = None) ParsedSubmission[source]
Select the input source and parse it into a
ParsedSubmission.Mirrors the previous inline dispatch in
translate_insert: a single--record-id/--targetpair wins, then--json-file, then--file(with--format), then--stdin. Raises BooktxError for any ambiguous or missing combination.
Translation-record acceptance service.
Centralizes the validate-and-persist flow that was duplicated between the
translate insert (batch) and translate set-record (single-record)
commands. Both commands used to re-implement the same steps: look up the
source view, validate each record against the current context, bail on the
first ERROR finding, then mutate the translation store with a shared
timestamp and refresh the status snapshot.
This module owns the pure workflow:
Resolve each submitted record id against the source index (raising
booktx.config.BooktxErrorfor duplicate / unknown / out-of-task ids, which the CLI renders exactly like any other user-facing error).Load the translation context once and validate every record against it.
If any validation produced an ERROR finding, raise
SubmissionValidationErrorcarrying those findings — the store is not touched.Otherwise mutate the store atomically with one shared timestamp and return an
AcceptResultdescribing the post-accept progress for the first affected chapter.
The CLI wrappers parse options, call accept_translation_records() (or
accept_one_record()), then render the result. Console output is
intentionally not produced here so the service is unit-testable without
Typer/Rich.
- class booktx.acceptance.SubmittedRecord(id: str, target: str)[source]
One validated submission item (record id + target text).
- id: str
- target: str
- class booktx.acceptance.AcceptResult(accepted_records: int, target_words: int, version_ref: str = '', chapter_id: str = '', chapter_title: str = '', records_translated: int = 0, records_total: int = 0, records_remaining: int = 0)[source]
Post-accept progress for the first affected chapter.
chapter_idis empty when the accepted record(s) could not be mapped to a chapter; the CLI treats that as “no chapter line to print”.- accepted_records: int
- target_words: int
- version_ref: str
- chapter_id: str
- chapter_title: str
- records_translated: int
- records_total: int
- records_remaining: int
- exception booktx.acceptance.SubmissionValidationError(findings: list[Finding])[source]
Raised when one or more submitted records failed ERROR-level validation.
Carries the ERROR findings so the CLI can render them with the existing submission-failure renderer. The translation store is never written when this is raised.
- booktx.acceptance.validate_submitted_records(proj: Project, bundle: StatusBundle, submitted: list[SubmittedRecord], *, task: TranslationTask | None, enforce_task_membership: bool = True) list[Finding][source]
Validate submitted records, raising BooktxError on id problems.
Context is loaded exactly once. Returns all findings (any severity); the caller decides whether ERROR findings should block the store write.
- booktx.acceptance.accept_translation_records(proj: Project, submitted: list[SubmittedRecord], *, bundle: StatusBundle, task: TranslationTask | None = None, submission_translation_version: str | None = None, submission_profile: str | None = None, enforce_task_version: bool = False) AcceptResult[source]
Validate and atomically persist a batch of accepted records.
Raises
BooktxErrorfor duplicate / unknown / out-of-task ids andSubmissionValidationErrorwhen any record fails ERROR-level validation. On success the store is written once and anAcceptResultis returned.Note: callers pass a fresh
bundlebuilt before acceptance. The returned progress fields reflect a refreshed snapshot taken after the store write, matching the historical CLI output exactly.
- booktx.acceptance.accept_one_record(proj: Project, record_id: str, target: str, *, bundle: StatusBundle, task: TranslationTask | None = None, submission_translation_version: str | None = None, submission_profile: str | None = None, enforce_task_version: bool = False) AcceptResult[source]
Validate and persist a single accepted record.
Equivalent to
accept_translation_records()with one item, but also enforces that a non-empty target was supplied (the single-record command rejects empty targets before reaching the store).
Rich/text/JSON output renderers.
Moved out of booktx.cli so command functions contain only option
parsing + service call + renderer call. Each renderer owns the exact console
output and can be unit-tested without Typer.
The module creates its own rich.console.Console so it never depends
on the CLI module (which would create a circular import).
- booktx.rendering.format_chunk_span(chunk_ids: list[str]) str[source]
Return a compact chunk-id range string for human display.
- booktx.rendering.print_status_human(bundle: StatusBundle, chapter: ChapterProgress | None) None[source]
Render the human-readable
booktx statussummary to the console.
- booktx.rendering.print_translate_task(task: TranslationTask, project: Project, *, mode: RuntimeMode | None = None, as_json: bool, output_format: str, show_sources: bool = False, show_template: bool = False) None[source]
Render a
translate next/translate taskresult to the console.Supports four output modes:
--json,--format tsv,--format block(the durable agent workflow), and the default human-readable list.
- booktx.rendering.render_submission_failures(findings: list[Finding]) None[source]
Render submission validation ERROR findings to the console.
Atomic write and timestamp helpers.
Centralizes the atomic-write pattern (write to a sibling temp file, then
replace) that was previously duplicated as the private
_write_json_atomic in booktx.config and reimplemented inline as
direct Path.write_text calls in several modules.
All booktx persistence (translation store, context, chapter map, manifest,
reports, chunks, task source files, and ingest templates) should route
through write_text_atomic() or write_json_model_atomic() so an
interrupted write never leaves a half-empty file in .booktx/.
- booktx.io_utils.utc_timestamp() str[source]
Return the current UTC time as a second-precision ISO-8601
Zstring.Equivalent to the inline expression that was duplicated in the translate acceptance path. Microseconds are dropped so timestamps are stable and human-comparable.
- booktx.io_utils.write_text_atomic(path: Path, text: str) None[source]
Write UTF-8
textatomically intopath.The file is written to a hidden sibling temp file in the same directory and then
replaced into place, so readers either see the previous file or the complete new file, never a partial write. Parent directories are created on demand. The temp file is cleaned up if the write fails.
- booktx.io_utils.write_json_model_atomic(path: Path, model: BaseModel, *, indent: int = 2, trailing_newline: bool = True) None[source]
Serialize a Pydantic
modelto JSON and write it atomically intopath.Mirrors the previous
model_dump_json(indent=2) + "\n"convention used by the store, chapter map, and chunk writers.by_aliasand other dump options must be applied by the caller viamodel.model_dump_jsonwhen needed; this helper is for the common default case.
- booktx.io_utils.write_json_text_atomic(path: Path, text: str) None[source]
Write already-serialized JSON text atomically into
path.Used when the caller has already produced JSON via
json.dumpsormodel_dump_json(by_alias=True)and just needs the atomic write plus a trailing newline. The trailing newline matches the historical convention.
Helpers for canonical record and version references.
- class booktx.record_refs.RecordRef(chunk_id: int, part_id: int)[source]
Parsed record reference with stable canonical rendering.
- chunk_id: int
- part_id: int
- property canonical_id: str
- property compact_ref: str
- class booktx.record_refs.VersionRef(version: int, subversion: int)[source]
Parsed dotted version reference with numeric ordering.
- version: int
- subversion: int
- property version_ref: str
- booktx.record_refs.canonical_record_id(chunk_id: int, part_id: int) str[source]
Return the canonical padded record id for integer ids.
- booktx.record_refs.format_version_ref(version: int, subversion: int) str[source]
Return the canonical dotted version reference.
- booktx.record_refs.parse_record_ref(value: str) RecordRef[source]
Parse a record reference into canonical integer parts.
- booktx.record_refs.parse_version_ref(value: str) VersionRef[source]
Parse and validate a dotted version reference.
- booktx.record_refs.resolve_record_range(value: str, *, ordered_record_ids: list[str], chapter_record_ids: dict[str, list[str]] | None = None) list[str][source]
Resolve one record range selector against source reading order.
Helpers for working with nested translation stores.
This module also hosts the legacy compatibility surface for the old
flat (v1) TranslationStore: legacy_store_to_v2() and
migrate_legacy_store() convert legacy stores into the nested v2 shape.
They are kept here, clearly named, so legacy import/export stays quarantined
from the active v2 store logic.
- class booktx.translation_store.MigrationResult(store: TranslationStoreV2, migrated_records: int, missing_source_ids: list[str])[source]
Result of converting a legacy flat store into the v2 nested shape.
- store: TranslationStoreV2
- migrated_records: int
- __init__(store: TranslationStoreV2, migrated_records: int, missing_source_ids: list[str]) None
- class booktx.translation_store.EffectiveCandidateError(rule: str, message: str)[source]
A blocking problem resolving the effective candidate for a record.
rulemirrors the validation rule names (active_review_missing,active_review_not_accepted,active_review_base_drift) so editor index errors stay aligned with build/validate findings.- rule: str
- message: str
- class booktx.translation_store.EffectiveCandidateSelection(candidate: TranslationCandidate | TranslationReviewCandidate, selected_kind: Literal['translation', 'review'], selected_ref: str, version_ref: str | None, review_ref: str | None, review_chain: list[str])[source]
Effective output candidate plus provenance metadata.
selected_kind/selected_refidentify which candidate produces the output.version_refis the base translation version the output derives from;review_refis the selected review ref when the output is a review candidate.review_chainis the ordered review chain (earliest first) for review selections and empty for direct translations.- candidate: TranslationCandidate | TranslationReviewCandidate
- selected_kind: Literal['translation', 'review']
- selected_ref: str
- booktx.translation_store.active_candidate(record: StoredTranslationRecordV2) TranslationCandidate | None[source]
Return the active candidate for a record, if any.
- booktx.translation_store.active_review_candidate(record: StoredTranslationRecordV2) TranslationReviewCandidate | None[source]
Return the usable active review candidate, or None if unusable.
A review is usable only when present, accepted, and chain-valid. Stale, rejected, cyclic, or invalid-pass-order active reviews are not returned.
- booktx.translation_store.effective_candidate_selection(record: StoredTranslationRecordV2, *, strict_active_review: bool = True) EffectiveCandidateSelection | EffectiveCandidateError | None[source]
Resolve the effective candidate plus selection metadata for a record.
With
strict_active_review=True(the default), a set-but-unusableactive_reviewreturns anEffectiveCandidateErrorinstead of silently falling back to the active translation. This is the safe mode for editor indexes, which must never mask an invalid active review. Withstrict_active_review=Falsean unusable active review falls back to the accepted active translation, mirroringeffective_target_candidate().Returns
Noneonly when there is no accepted effective candidate.
- booktx.translation_store.effective_target_candidate(record: StoredTranslationRecordV2) TranslationCandidate | TranslationReviewCandidate | None[source]
Resolve the effective output target for a record.
Prefers the active review candidate when present and chain-valid; otherwise falls back to the active translation version.
- booktx.translation_store.ensure_store_record(store: TranslationStoreV2, record_ref: RecordRef | str, *, source: str, source_sha256: str) StoredTranslationRecordV2[source]
Return the v2 store record, creating it when needed.
- booktx.translation_store.find_candidate(record: StoredTranslationRecordV2, version_ref: str) TranslationCandidate | None[source]
Return the matching candidate, if present.
- booktx.translation_store.find_review_candidate(record: StoredTranslationRecordV2, review_ref: str) TranslationReviewCandidate | None[source]
Return the matching review candidate, if present.
- booktx.translation_store.migrate_legacy_store(legacy: TranslationStore, *, source_records: dict[str, SourceRecordView], version_ref: str = '1.1') MigrationResult[source]
Convert a legacy store into v2 and report any records missing source data.
- booktx.translation_store.legacy_store_to_v2(legacy: TranslationStore, *, source_records: dict[str, SourceRecordView] | None = None) TranslationStoreV2[source]
Convert a legacy flat store into the nested v2 shape in memory.
- booktx.translation_store.resolve_review_base(record: StoredTranslationRecordV2, base_kind: Literal['translation', 'review'], base_ref: str) TranslationCandidate | TranslationReviewCandidate | None[source]
Resolve the base candidate a review was derived from.
- booktx.translation_store.review_candidate_is_stale(record: StoredTranslationRecordV2, review: TranslationReviewCandidate) bool[source]
Return True when the direct base of a review is missing or drifted.
- booktx.translation_store.review_chain_is_stale(record: StoredTranslationRecordV2, review_ref: str) bool[source]
Return True when any base in the review derivation chain is missing or drifted.
Walks from
review_refback to the translation base, rejecting missing bases, target-hash mismatches, and cycles.
- booktx.translation_store.review_chain_refs(record: StoredTranslationRecordV2, review_ref: str) list[str] | None[source]
Return the ordered review chain ending at
review_ref(earliest first).The chain excludes the translation base. For
R2.1based onR1.1based on a translation version, returns["R1.1", "R2.1"]. ReturnsNonewhen the chain is missing, stale (base_target_sha256drift), cyclic, or violates the lexicographic(pass, run)order (so same-pass reruns such asR1.2fromR1.1are valid).
- booktx.translation_store.sha256_text(text: str) str[source]
Return the canonical SHA256 hex digest for a target/base text.
- booktx.translation_store.upsert_translation_version(record: StoredTranslationRecordV2, version_ref: str, target: str, *, updated_at: str, status: str = 'accepted', activate: bool = False, baseline_ref: str | None = None, baseline_sha256: str | None = None, context_view_sha256: str | None = None, context_view_path: str | None = None, context_notes_scope: str | None = None, context_target_chapter_id: str | None = None, context_notes_through_chapter_id: str | None = None) TranslationCandidate[source]
Insert or update a candidate version on one record.
Version-ledger identity, context hashing, and version resolution helpers.
- class booktx.versioning.VersionResolution(ledger: TranslationVersionLedger, identity: TranslationIdentity, version_ref: str, version: int, subversion: int, baseline_sha256: str, context_sha256: str, created_track: bool, created_subversion: bool)[source]
Resolved current translation version metadata.
- ledger: TranslationVersionLedger
- identity: TranslationIdentity
- version_ref: str
- version: int
- subversion: int
- baseline_sha256: str
- context_sha256: str
- created_track: bool
- created_subversion: bool
- __init__(ledger: TranslationVersionLedger, identity: TranslationIdentity, version_ref: str, version: int, subversion: int, baseline_sha256: str, context_sha256: str, created_track: bool, created_subversion: bool) None
- booktx.versioning.canonical_json_sha256(data: object) str[source]
Hash canonical JSON with stable key ordering and separators.
- booktx.versioning.current_baseline_sha256(project: Project) str[source]
Return the semantic baseline hash for the current project context.
- booktx.versioning.current_context_sha256(project: Project) str[source]
Return the canonical context hash for the current project context.
- booktx.versioning.default_identity() TranslationIdentity[source]
Return deterministic local defaults when no identity file exists.
- booktx.versioning.fork_current_context(project: Project, *, note: str | None = None, context_label: str | None = None) VersionResolution[source]
Force a new subversion even when the current baseline hash is unchanged.
- booktx.versioning.lookup_version(ledger: TranslationVersionLedger, version_ref: str) tuple[TranslationTrackLedgerEntry, TranslationSubversionLedgerEntry][source]
Resolve one dotted version reference against the ledger.
- booktx.versioning.resolve_current_version(project: Project, *, actor: str | None = None, harness: str | None = None, model: str | None = None, force_new_context: bool = False, context_label: str | None = None, note: str | None = None) VersionResolution[source]
Resolve and persist the current ledger version for a translation write.
- booktx.versioning.resolve_identity(project: Project, *, actor: str | None = None, harness: str | None = None, model: str | None = None) TranslationIdentity[source]
Resolve identity from explicit overrides, stored defaults, and fallbacks.
- booktx.versioning.select_active_version(project: Project, version_ref: str) TranslationVersionLedger[source]
Select the project-wide active version in the ledger.
- booktx.versioning.set_track_label(project: Project, major_version: int, label: str) TranslationVersionLedger[source]
Set the label for one major track.