Translate a note's transcript into a chosen language (Claude provider, stored beside the transcript) - #48
Merged
Conversation
…der boundary New apps.translations module: NoteTranslation rows per (note, target language), a TranslationProvider protocol with a Claude implementation using structured output, a Celery task that validates the provider output against the unit contract before storing text and per-segment translations, and a REST adapter at /api/notes/<id>/translations/ (GET reports whether the server can translate; POST dispatches or returns the stored row). The transcript is never modified. Frontend: TranslationPanel on the note page with disabled, empty, pending, failed, and completed states, including translated speaker turns aligned by segment id; shared transcript helpers moved to utils/transcript.ts. Governance: capability contract, deterministic eval suite (marker ai_eval, fails closed without ANTHROPIC_API_KEY), ADR-005, .claude/verification.json, README env and testing notes. Compose files pass the new env through.
…the provider boundary - Re-transcription and a manual transcript edit now call the translations module's public invalidate_translations_for_note; stale rows were shown under a fresh header and their per-turn view broke on the new segment ids. - The Claude provider uses messages.create with a JSON-schema output_config and validates the text itself, so truncation (stop_reason max_tokens) and malformed output become controlled failures instead of a pydantic exception escaping the boundary; failure reasons never carry model output. - Retry policy: transient provider failures (rate limit, 5xx, connection) are retried with the row left pending; refusals, truncation, schema and contract violations, and configuration errors fail fast. Unexpected exceptions are logged by type only. - Compose passes CLIO_TRANSLATION_EFFORT and CLIO_TRANSLATION_TIMEOUT_SECONDS. - TranslationPanel renders a load-error state with Retry instead of vanishing. - Tests: invalidation on retranscribe/edit, segments untouched, retry policy, provider boundary with a faked SDK client; Playwright load-error case. - Contract and ADR updated with invalidation and retry policy.
…schema definition - Task writes are conditional UPDATEs on the row pk; zero rows means the note was re-transcribed or edited while the provider call was in flight and the result (or failure) is discarded. A plain save() re-inserted the stale row. - OUTPUT_SCHEMA is derived from _TranslationOut (refs inlined, titles dropped, objects closed) and a test binds it to the SDK's own transform of the model. - Unexpected task exceptions are logged by type without a traceback, and the test observes the real log records through caplog's handler.
QuerySet.update() bypasses auto_now, so the row's updated_at froze at creation; the panel orders translations by it and it is provenance for the artifact.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Slice 2 of the multilingual work. The transcript stays in the spoken language (ADR-004); this adds Translate to as a separate stored artifact.
backend/apps/translations/:NoteTranslation(one row per note and target language, unique),TranslationProviderboundary with a Claude implementation (anthropic1.3.0,messages.parsewith a pydanticoutput_format), a Celery task that validates the provider output against the input units (same ids, order, count, no empty text) before storing full text plus per-segment translations, andGET|POST /api/notes/<id>/translations/.TranslationPanelon the note page with disabled / empty / pending / failed / completed states, translated speaker turns aligned by segment id. Shared transcript helpers moved toutils/transcript.ts.docs/ai/translation-capability-contract.md,docs/ai/translation-eval-suite.md,docs/adr/005-translation-provider.md, eval suitebackend/evals/translation(markerai_eval, fails closed withoutANTHROPIC_API_KEY),.claude/verification.json.ANTHROPIC_API_KEYandCLIO_TRANSLATION_MODELto backend and celery. Without the key the endpoint answers 503 and the UI shows "Translation is not enabled on this server."Why a second key
Deepgram transcribes only. Translation is an LLM call. See ADR-005.
Status: capability NOT READY
No Anthropic key exists in the vault yet, so the eval suite and the live request shape are unverified. Everything else is proven with a faked provider. Do not enable in production until
pytest evals/translation -m ai_evalpasses with a real key.Evidence
pytest167 passed (28 new intests/test_translations.py);ruffclean;mypy --python-version 3.12 apps/translationsclean.tsc --noEmitclean; eslint clean on touched files; Playwrighte2e/tests/translation.spec.js3 mocked-API state tests pass (screenshot inspected), live test skipped without keys.Depends on #47 for green CI (
anthropicneeds Python 3.10+; CI moves to 3.12 there).Review and CI (updated)